Angular 2 – Surcharger un service Angular

de | décembre 29, 2016

Dans ce deuxième tutoriel nous allons voir comment surcharger un service Angular2. Comme exemple, nous allons prendre le service http permettant de réaliser des appels REST vers un serveur. Cela nous permettra de pouvoir enregistrer les appels au serveur ou encore d’effectuer une redirection vers un formulaire de connexion à la suite du retour d’un code 401 de la part du serveur. Contrairement aux « Guard » qui pourraient également réaliser ce rôle, surcharger le service http présente l’avantage de ne pas nécessiter de requêtes supplémentaires pour vérifier l’acceptation de celle-ci par le serveur.

Surcharge et héritage

Angular2 utilise Typescript. Ce dernier nous permet de bénéficier de l’héritage de classe très simplement. Nous allons donc commencer par créer un service héritant du service http d’Angular2.


import { Injectable } from '@angular/core';
import {
   Http,
   ConnectionBackend,
   RequestOptions,
   RequestOptionsArgs,
   Response,
   Request,
   Headers
} from '@angular/http';
import { ConfService } from '../conf/conf.service';
import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import 'rxjs/Rx';

@Injectable()
export class HttpService extends Http {
   constructor(backend: ConnectionBackend,
       defaultOptions: RequestOptions, private router: Router, private conf: ConfService) {
       super(backend, defaultOptions);
   }

/**
* Performs any type of http request.
* @param url
* @param options
* @returns {Observable<Response>}
*/
request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
    return super.request(url, options);
}

/**
* Performs a request with `get` http method.
* @param url
* @param options
* @returns {Observable<any>}
*/
get(url: string, options?: RequestOptionsArgs): Observable<any> {

    return super.get(url, this.requestOptions(options))
        .catch(this.onCatch)
        .do((res: Response) => {
            this.onSubscribeSuccess(res);
        }, (error: any) => {
            this.onSubscribeError(error);
        })
        .finally(() => {
            this.onFinally();
        });
}

getLocal(url: string, options?: RequestOptionsArgs): Observable<any> {
    return super.get(url, options);
}

/**
* Performs a request with `post` http method.
* @param url
* @param body
* @param options
* @returns {Observable<any>}
*/
post(url: string, body: any, options?: RequestOptionsArgs): Observable<any> {
    return super.post(url, body, this.requestOptions(options))
    .catch(this.onCatch)
    .do((res: Response) => {
        this.onSubscribeSuccess(res);
    }, (error: any) => {
        this.onSubscribeError(error);
    })
    .finally(() => {
        this.onFinally();
    });
}

/**
* Performs a request with `put` http method.
* @param url
* @param body
* @param options
* @returns {Observable<any>}
*/
put(url: string, body: string, options?: RequestOptionsArgs): Observable<any> {
    return super.put(url, body, this.requestOptions(options))
    .catch(this.onCatch)
    .do((res: Response) => {
        this.onSubscribeSuccess(res);
    }, (error: any) => {
        this.onSubscribeError(error);
    })
    .finally(() => {
        this.onFinally();
    });
}

/**
* Performs a request with `delete` http method.
* @param url
* @param options
* @returns {Observable<any>}
*/
delete(url: string, options?: RequestOptionsArgs): Observable<any> {
    return super.delete(url, options)
    .catch(this.onCatch)
    .do((res: Response) => {
        this.onSubscribeSuccess(res);
    }, (error: any) => {
        this.onSubscribeError(error);
    })
    .finally(() => {
       this.onFinally();
    });
}
/**
* Request options.
* @param options
* @returns {RequestOptionsArgs}
*/
private requestOptions(options?: RequestOptionsArgs): RequestOptionsArgs {
    if (options == null) {
        options = new RequestOptions();
    }
    if (options.headers == null) {
        options.headers = new Headers();
    }
    return options;
}

/**
* Error handler.
* @param error
* @param caught
* @returns {ErrorObservable}
*/
private onCatch(error: any, caught: Observable<any>): Observable<any> {
    return Observable.throw(error);
}

/**
* onSubscribeSuccess
* @param res
*/
private onSubscribeSuccess(res: Response): void {
}

/**
* onSubscribeError
* @param error
*/
private onSubscribeError(error: any): void {
    if (error.status === 401) {
        this.conf.deleteJWT();
        this.router.navigate(['/login']);
    }
}

/**
* onFinally
*/
private onFinally(): void {

}
}

Cela fait beaucoup de code. La raison est que nous surchargeons les méthodes GET, PUT, POST et DELETE.


@Injectable()
export class HttpService extends Http {
    constructor(backend: ConnectionBackend,
        defaultOptions: RequestOptions, private router: Router, private conf: ConfService) {
        super(backend, defaultOptions);
    }
}

Nous rendons le service injectable grâce à l’annotation @Injectable. Nous héritons ensuite du service Http d’Angular2. Pour finir nous passons les paramètres attendus par le constructeur du service Http. J’ai rajouté le service ConfService que j’utilise pour gérer les tokens JWT.


.do((res: Response) => {
    this.onSubscribeSuccess(res);
}, (error: any) => {
    this.onSubscribeError(error);
})

Nous utilisons les méthodes this.onSubscribeSuccess(res) et this.onSubscribeError(error) pour interagir avec le résultat de la requête faite par le service. Dans notre cas la méthode intéressante est la méthode onSubscribeSuccess(res).


private onSubscribeError(error: any): void {
    if (error.status === 401) {
        this.conf.deleteJWT();
        this.router.navigate(['/login']);
    }
}

Nous récupérons le status de l’erreur. Si celui-ci est 401, on supprime le token JWT de la session utilisateur et on le redirige vers l’écran de connexion. Voyons maintenant comment dire à Angular2 d’utiliser notre service à la place du service Http par défaut. Cela se passe dans le CoreModule. Je vous encourage, si cela n’est pas déjà fait, à lire le précédent tutoriel sur les modules d’Angular2.


import { HttpService } from './services/http/http.service';
import {
   Http,
   XHRBackend,
   RequestOptions,
} from '@angular/http';

@NgModule({
   imports: [
       CommonModule
   ],
   providers: [
       // others services to provide.
       ...
       {
           provide: Http,
           useFactory: httpFactory,
           deps: [XHRBackend, RequestOptions, Router, ConfService]
       }
   ],
})

export class CoreModule {

    // Core service code
    ...
}

export function httpFactory(xhrBackend: XHRBackend, requestOptions:
    RequestOptions, router: Router, conf: ConfService) {
    return new HttpService(xhrBackend, requestOptions, router, conf);
}

Nous utilisons une factory pour fournir notre HttpService au provider angular. En spécifiant Http comme nom au paramètre provide, nous surchargeons le service Http par défaut de Angular2 avec le résultat retourné par notre factory définie grâce au paramètre useFactory. Enfin le dernier paramètre deps, nous permet de spécifier les paramètres à passer à notre factory pour que celle-ci puissent réaliser sa fonction. Comme vous pouvez le remarquer, je passe en paramètre le ConfService.

Vous savez maintenant comment surcharger n’importe quels services Angular2. Le meilleur dans cette méthode, est qu’elle est entièrement transparente au niveau du code existant. En effet il suffira d’injecter le service Http pour que Angular2 le remplace automatique par notre service.

A bientôt.

02

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.