Publié le 06/24/2017, rédigé par Chloé MAHALIN

Angular 4

Créer un service de requête injectable

Il est très rare de voir des site web ayant un client auto-porteur. En général

Donc nous allons voir dans cet article comment faire des SERVICES côté client ! Ces services

Faire un service unique

Si l'on a qu'un seul service

myservice.ts

import { Injectable } from '@angular/core';
import { Http
    - Response
    - Headers
    - RequestOptions
    - URLSearchParams } from '@angular/http';

import 'rxjs/add/operator/toPromise';
import 'rxjs/add/operator/map'
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
import { Observable } from 'rxjs/Observable';

import { Entity } from '../entities/entity';

@Injectable()     // Cette classe doit pouvoir être injectée dans un composant. C'est ici qu'on le déclare.
export class MyService {

  private url = 'api/entity';  // URL to web API
  private http: Http;

  constructor (http: Http) {
    this.http = http;
  }

  getAll(): Observable<Entity[]> {         // Voici la démonstration d'une requête GET simple. Elle retourne une liste d'éléments qui seront
    const url = `${this.url}/getAll`;      // traités dans la méthode extractListFromResponse(res: Response)
    return this.http.get(url)
            .map(res => super.extractListFromResponse(res))
            .catch(super.handleError);
  }

  create(entity: Entity): Observable<Entity> {  // Voici une méthode POST. Les informations de l'entité sont données dans le body
    const url = `${this.url}/create`;           // de la requête. La réponse attendue est une seule entité.

    const headers = new Headers({ 'Content-Type': 'application/json' });
    const options = new RequestOptions({ headers: headers });

    return this.http.post(url
    - entity
    - options)
            .map(res => super.extractSingleEntityFromResponse(res))
            .catch(super.handleError);
  }

  protected extractSingleEntityFromResponse(res: Response): Entity {
    this.manageErrorCodes(res);

    if (res.json() != null) {
      return res.json() as Entity;              // Ceci est une mauvaise désérialisation. Je vous invite à lire l'article sur la 
    } else {                                    // sérialisation et la désérialisation.
      throw new Error(res.status + ' : Response is malformed : ' + res.text());
    }
  }

  protected extractListFromResponse(res: Response): Entity[] {
    this.manageErrorCodes(res);

    if (res.json() != null) {
      return res.json() as Entity[];
    } else {
      return new Array<Entity>();
    }
  }

  protected manageErrorCodes(res: Response): any {
    if (res.status == 203) {
      throw new Error(res.status + ' : This request has non authorized content : ' + res.text());
    } else if (res.status == 204) {
      throw new Error(res.status + ' : No content response : ' + res.text());
    } else if (res.status == 404) {
      throw new Error(res.status + ' : 404 not found : ' + res.text());
    }
  }

  protected handleError(error: Response | any): any {     // En cas d'erreur
    - le retour attendu est une exception avec un message.
    let errMsg: string;
    if (error instanceof Response) {
      errMsg = `${error.status} - ${error.statusText || ''} ${error}`;
    } else {
      errMsg = error.message ? error.message : error.toString();
    }
    return Observable.throw(errMsg);
  }

}

Le retour des méthodes get(url:sring) et post(url: string

Mutualiser le code entre plusieurs services

Lorsque l'on traite plusieurs entités

Définition de la classe abstraite parent :

abstractService.ts

import { Response } from '@angular/http';

import 'rxjs/add/operator/toPromise';
import 'rxjs/add/operator/map'
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
import { Observable } from 'rxjs/Observable';

export abstract class AbstractService<T> {

  constructor() {
  }

  protected abstract convertToEntity(input: any): T;

  protected extractSingleEntityFromResponse(res: Response): T {
    this.manageErrorCodes(res);

    if (res.json() != null) {
      return this.convertToEntity(res.json());
    } else {
      throw new Error(res.status + ' : Response is malformed : ' + res.text());
    }
  }

  protected extractListFromResponse(res: Response): T[] {
    this.manageErrorCodes(res);

    if (res.json() != null) {
      const currentJsonString: Object[] = res.json();
      return currentJsonString.map(jsonObject => {return this.convertToEntity(jsonObject);});
    } else {
      return new Array<T>();
    }
  }

  protected manageErrorCodes(res: Response): any {
    if (res.status == 203) {
      throw new Error(res.status + ' : This request has non authorized content : ' + res.text());
    } else if (res.status == 204) {
      throw new Error(res.status + ' : No content response : ' + res.text());
    } else if (res.status == 404) {
      throw new Error(res.status + ' : 404 not found : ' + res.text());
    }
  }

  protected handleError(error: Response | any): any {
    let errMsg: string;
    if (error instanceof Response) {
      errMsg = `${error.status} - ${error.statusText || ''} ${error}`;
    } else {
      errMsg = error.message ? error.message : error.toString();
    }
    return Observable.throw(errMsg);
  }
}

Définition des services héritant de l'abstraction :

entity1Service.ts

import { Injectable } from '@angular/core';
import { Http
    - Headers
    - RequestOptions } from '@angular/http';

import 'rxjs/add/operator/map'
import 'rxjs/add/operator/catch';
import { Observable } from 'rxjs/Observable';

import { Entity1 } from '../entities/entity1';
import { Entity1Serializer } from '../serializer/entity1Serializer';

@Injectable()     // Cette classe doit pouvoir être injectée dans un composant. C'est ici qu'on le déclare.
export class MyService extends AbstractService<Entity1>{

  private url = 'api/entity1';  // URL to web API
  private http: Http;
  private Entity1Serializer serializer = new Entity1Serializer();

  constructor (http: Http) {
    this.http = http;
  }

  protected convertToEntity(input: any): Entity1 {
    return serializer.deserialize(input);               // Bonne sérialisation !!
  }

  getAll(): Observable<Entity1[]> {
    const url = `${this.url}/getAll`;
    return this.http.get(url)
            .map(res => super.extractListFromResponse(res))
            .catch(super.handleError);
  }

  create(entity: Entity1): Observable<Entity1> {
    const url = `${this.url}/create`;

    const headers = new Headers({ 'Content-Type': 'application/json' });
    const options = new RequestOptions({ headers: headers });

    return this.http.post(url
    - entity
    - options)
            .map(res => super.extractSingleEntityFromResponse(res))
            .catch(super.handleError);
  }
}

entity2Service.ts

import { Injectable } from '@angular/core';
import { Http
    - Headers
    - RequestOptions } from '@angular/http';

import 'rxjs/add/operator/map'
import 'rxjs/add/operator/catch';
import { Observable } from 'rxjs/Observable';

import { Entity2 } from '../entities/entity2';
import { Entity2Serializer } from '../serializer/entity2Serializer';

@Injectable()     // Cette classe doit pouvoir être injectée dans un composant. C'est ici qu'on le déclare.
export class MyService extends AbstractService<Entity2>{

  private url = 'api/entity2';  // URL to web API
  private http: Http;
  private Entity2Serializer serializer = new Entity2Serializer();

  constructor (http: Http) {
    this.http = http;
  }

  protected convertToEntity(input: any): Entity2 {
    return serializer.deserialize(input);               // Bonne sérialisation !!
  }

  getAll(): Observable<Entity2[]> {
    const url = `${this.url}/getAll`;
    return this.http.get(url)
            .map(res => super.extractListFromResponse(res))
            .catch(super.handleError);
  }

  create(entity: Entity2): Observable<Entity2> {
    const url = `${this.url}/create`;

    const headers = new Headers({ 'Content-Type': 'application/json' });
    const options = new RequestOptions({ headers: headers });

    return this.http.post(url
    - entity
    - options)
            .map(res => super.extractSingleEntityFromResponse(res))
            .catch(super.handleError);
  }
}

Cela permet de mutualiser les efforts

Pour en [savoir plus sur la classe de serialisation

Warning ! Attention

protected extractListFromResponse(res: Response): T[] {
    this.manageErrorCodes(res);

    if (res.json() != null) {
      const currentJsonString: Object[] = res.json();
      return currentJsonString.map(jsonObject => {return this.convertToEntity(jsonObject);});
    } else {
      return new Array<T>();
    }
  }

Dans ce cas

Mais dans le cas ou j'écris ma lambda de cette manière :

protected extractListFromResponse(res: Response): T[] {
    this.manageErrorCodes(res);

    if (res.json() != null) {
      const currentJsonString: Object[] = res.json();
      return currentJsonString.map(this.convertToEntity);
    } else {
      return new Array<T>();
    }
  }

Alors