Comprenez le fonctionnement des intercepteurs Angular basés sur une classe : implémentation de HttpInterceptor, injection de dépendances, chaînage et cas d'usage courants.
Résumé : l'intercepteur basé sur une classe
Avant Angular 15, les intercepteurs HTTP étaient obligatoirement définis comme une classe injectable implémentant l'interface HttpInterceptor. Cette approche, introduite avec HttpClientModule dans Angular 4.3, est encore très présente dans les codebases Angular existants.
Fonctionnement général :
- Un intercepteur intercepte toutes les requêtes HTTP sortantes et les réponses entrantes
- Il peut modifier les requêtes, ajouter des headers, logger, gérer les erreurs
- Les intercepteurs sont chaînés dans l'ordre d'enregistrement
- Chaque intercepteur doit appeler
next.handle(req)pour passer la requête au suivant
Implémenter HttpInterceptor
Un intercepteur classe implémente l'interface HttpInterceptor et sa méthode intercept().
import { Injectable } from '@angular/core';
import {
HttpInterceptor,
HttpRequest,
HttpHandler,
HttpEvent
} from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
// 1. Cloner la requête (immuable)
const authReq = req.clone({
setHeaders: {
Authorization: `Bearer ${this.getToken()}`
}
});
// 2. Passer la requête modifiée au prochain intercepteur
return next.handle(authReq);
}
private getToken(): string {
return localStorage.getItem('token') ?? '';
}
}
req.clone() qui retourne une nouvelle instance.
Enregistrement dans le module
L'intercepteur doit être enregistré avec le token HTTP_INTERCEPTORS dans le module principal, avec multi: true pour permettre plusieurs intercepteurs.
// app.module.ts
import { NgModule } from '@angular/core';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthInterceptor } from './interceptors/auth.interceptor';
@NgModule({
imports: [HttpClientModule],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true // indispensable pour permettre plusieurs intercepteurs
}
]
})
export class AppModule {}
Pour les applications standalone (Angular 14+) avec l'ancien style :
// main.ts — app standalone avec intercepteur classe
import { bootstrapApplication } from '@angular/platform-browser';
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
bootstrapApplication(AppComponent, {
providers: [
provideHttpClient(withInterceptorsFromDi()),
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true
}
]
});
Injection de dépendances dans un intercepteur
L'un des avantages de l'approche classe est l'accès facile à l'injection de dépendances via le constructeur.
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { Router } from '@angular/router';
import { AuthService } from '../services/auth.service';
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
// Injection via constructeur
constructor(
private router: Router,
private authService: AuthService
) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
catchError((error: HttpErrorResponse) => {
if (error.status === 401) {
this.authService.logout();
this.router.navigate(['/login']);
}
if (error.status === 403) {
this.router.navigate(['/forbidden']);
}
return throwError(() => error);
})
);
}
}
Cas d'usage courants
1. Intercepteur de logging :
@Injectable()
export class LoggingInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const start = Date.now();
console.info(`[HTTP] ${req.method} ${req.url}`);
return next.handle(req).pipe(
tap({
next: (event) => {
if (event instanceof HttpResponse) {
console.info(`[HTTP] ${req.url} — ${Date.now() - start}ms`);
}
}
})
);
}
}
2. Intercepteur d'en-tête de langue :
@Injectable()
export class LanguageInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const lang = localStorage.getItem('lang') || 'fr';
return next.handle(req.clone({
setHeaders: { 'Accept-Language': lang }
}));
}
}
Chaînage de plusieurs intercepteurs
L'ordre d'enregistrement dans le tableau providers définit l'ordre d'exécution : le premier enregistré est le premier exécuté pour les requêtes, et le dernier pour les réponses.
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }, // 1er
{ provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true }, // 2ème
{ provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true }, // 3ème
]
// Ordre d'exécution pour une REQUÊTE :
// AuthInterceptor → LoggingInterceptor → ErrorInterceptor → Serveur
// Ordre d'exécution pour une RÉPONSE :
// Serveur → ErrorInterceptor → LoggingInterceptor → AuthInterceptor
Limites de l'approche classe
- Verbeux : nécessite une classe complète, un décorateur
@Injectable()et un enregistrement explicite - Injection circulaire : injecter
HttpClientdans un intercepteur (ex: pour rafraîchir un token) peut causer des dépendances circulaires - Non tree-shakeable : les intercepteurs classe sont toujours inclus dans le bundle même s'ils ne sont pas utilisés dans certaines routes
- Tests plus verbeux : nécessitent plus de setup dans
TestBed
HttpInterceptorFn — voir l'article sur le nouvel intercepteur fonctionnel.