Les intercepteurs Angular
Comme à peu près tous les frameworks Angular offre la possibilité de créer des intergiciels (middlewares) qui permettent d’intercepter une requête. Ceci nous permet d’avoir un point unique qui sera appelé chaque fois qu’une requête est faite depuis votre code. Aujourd’hui, on va explorer les intercepteurs d’Angular.
C’est quoi un middleware?
Un intergiciel se place entre deux couches logicielles, voire entre une couche logicielle et physique. Concentrons-nous sur la première définition.
Par couches logicielles, on entend ici les différentes couches de votre application, mais aussi de celles du framework. Un intercepteur s’applique entre la couche http d’Angular et le transport de la requête sur le réseau. Vous avez donc accès aux données de la requête, son corps, ses entêtes, etc..
Création d’un intercepteur avec Angular cli
Depuis la racine d’un projet Angular existant, exécutez :
1 |
ng g interceptor interceptor-name-you-want |
Puis, dans les providers de votre module, ajouter cette entrée :
1 2 3 4 5 |
{ provide: HTTP_INTERCEPTORS, useClass: InterceptorNameYouWantInterceptor, multi: true, } |
À ce moment, votre application angular vous avez un intercepteur qui est exécuté à chaque fois que votre application fait une requête. Le cli d’Angular vous a aussi créé un fichier contenant l’intercepteur :
1 2 3 4 5 6 7 8 9 |
@Injectable() export class InterceptorNameYouWantInterceptor implements HttpInterceptor { constructor() {} intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> { return next.handle(request); } } |
Comment avoir plusieurs intercepteurs?
En supposant que vous aillez besoin de plusieurs intercepteurs pour, par exemple, vous conformer au principe de responsabilité unique des principes SOLID, vous pourriez chaîner plusieurs intercepteurs qui seront exécutés dans l’ordre qu’ils sont définis. Pour faire ça simple et propre, créer un fichier qui exporte un tableau des providers comme ceci :
1 2 3 4 5 6 7 |
export const interceptorProviders = [ { provide: HTTP_INTERCEPTORS, useClass: TestInterceptor1, multi: true }, { provide: HTTP_INTERCEPTORS, useClass: TestInterceptor2, multi: true }, { provide: HTTP_INTERCEPTORS, useClass: TestInterceptor3, multi: true }, { provide: HTTP_INTERCEPTORS, useClass: TestInterceptor4, multi: true }, { provide: HTTP_INTERCEPTORS, useClass: TestInterceptor5, multi: true }, ]; |
Puis dans les providers de votre modules :
1 |
providers: [interceptorProviders] |
Concrètement, à quoi ça sert
1. Injecter un jeton de sécurité dans les entêtes http
Toute bonne application monopage (single page application) qui nécessite une quelconque forme d’identification va avoir besoin d’une jeton de sécurité JWT. Je ne m’attarderez pas trop à ce que c’est et comment ça marche parce que j’ai déjà traité le sujet par le passé, mais c’est avec un intercepteur que vous pourriez vouloir injecter un jeton oAuth dans les entêtes des requêtes vers vos services API.
Par exemple, en supposant que sessionService.getUserToken() vous retourne un JWT encodé, l’intercepteur ressemblerait à :
1 2 3 4 5 6 7 8 9 10 11 12 |
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { const headers = { Authorization: `Bearer ${this.sessionService.getUserToken()}`, 'Content-Type': 'application/json' }; request = request.clone({ setHeaders: headers }); return next.handle(request); } |
2. Ajouter de la journalisation
À des fins de débogage, vous pourriez vouloir journaliser le contenu des requêtes ainsi que l’URL. Vous pourriez même profiler le temps d’exécution avec un intercepteur comme celui-ci :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { const started = Date.now(); let ok: string; return next.handle(request).pipe( tap( (event: HttpEvent<any>) => ok = event instanceof HttpResponse ? 'success' : '', (error: HttpErrorResponse) => ok = "error" ), finalize(() => { const elapsed = Date.now() - started; const msg = `${request.method} "${request.urlWithParams}" ${ok} executed in ${elapsed} ms.`; console.log(msg); }) ); } |
3. Appliquer une conversion
Que ce soit avant d’envoyer la requête au backend ou en recevant la réponse, il serait possible de convertir les données du corps de la requête. Par exemple, vous faites une requête pour un objet qui contient une date. Même si votre interface/classe TypeScript définit la propriété comme étant une date, si vous essayez de faire .getTime() dessus, vous allez obtenir une erreur parce que le type est en fait une string. Un intercepteur peut nous aider :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
const dateRegEx = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$/; intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { return next.handle(request).pipe( filter(event => event instanceof HttpResponse), tap((event: HttpResponse<any>) => { if (event.body) { const body = event.body; const bodyKeys = Object.keys(body); bodyKeys.forEach(key => { const keyTypeIsString = typeof body[key] === 'string'; const valueMatchesDateRegEx = (body[key] as string).match(dateRegEx); if (keyTypeIsString && valueMatchesDateRegEx) { body[key] = new Date(body[key]); } }) } }) ); } |
Conclusion
Aujourd’hui, vous avez vu ce qu’est un intercepteur dans Angular et vous avez vu 3 façons différentes de les utiliser. Si vous avez d’autres idées, n’hésitez pas à me les partager en commentaires! Sinon, comme à l’habitude, partagez sur vos réseaux sociaux 🙂
Cheers!
Commentaires
Laisser un commentaire