Angular : nouvel intercepteur fonctionnel (Angular 15+)

🏷️ Front-end 📅 13/04/2026 10:00:00 👤 Mezgani said
Angular Interceptor Http Angular 15 Functional
Angular : nouvel intercepteur fonctionnel (Angular 15+)

Maîtrisez les intercepteurs fonctionnels d'Angular 15+ : syntaxe avec HttpInterceptorFn, withInterceptors(), gestion des erreurs, retry et comparaison avec l'ancien style classe.

Résumé : l'intercepteur fonctionnel

Introduit dans Angular 15, le style fonctionnel remplace la classe HttpInterceptor par une simple fonction typée HttpInterceptorFn. Cette approche est plus concise, tree-shakeable, et s'intègre naturellement dans les applications standalone.

À retenir : Le nouvel intercepteur fonctionnel est le style recommandé pour toute nouvelle application Angular 15+. Il remplace avantageusement la classe sans perte de fonctionnalité.

Avantages par rapport à l'ancien style :

  • Moins verbeux : une fonction au lieu d'une classe + décorateur + enregistrement complexe
  • Tree-shakeable : l'intercepteur n'est inclus dans le bundle que s'il est utilisé
  • inject() : injection de dépendances sans constructeur
  • Pas de circular dependency : injecter HttpClient dans l'intercepteur fonctionne sans problème
  • Idéal pour standalone : s'enregistre directement dans provideHttpClient()

Syntaxe de base HttpInterceptorFn

Un intercepteur fonctionnel est une fonction qui prend la requête et le gestionnaire suivant, et retourne un observable.

import { HttpInterceptorFn } from '@angular/common/http';

export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const token = localStorage.getItem('token') ?? '';

  const authReq = req.clone({
    setHeaders: {
      Authorization: `Bearer ${token}`
    }
  });

  return next(authReq);
};
Signature : HttpInterceptorFn = (req: HttpRequest<unknown>, next: HttpHandlerFn) => Observable<HttpEvent<unknown>>. Le typage est plus simple et direct qu'avec la classe.

Enregistrement avec withInterceptors()

Les intercepteurs fonctionnels s'enregistrent via withInterceptors() dans provideHttpClient(), directement dans main.ts ou app.config.ts.

// app.config.ts — application standalone
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { authInterceptor } from './interceptors/auth.interceptor';
import { loggingInterceptor } from './interceptors/logging.interceptor';

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(
      withInterceptors([
        authInterceptor,    // 1er
        loggingInterceptor  // 2ème
      ])
    )
  ]
};

Pour une application avec AppModule (non-standalone) :

// app.module.ts avec intercepteur fonctionnel
import { NgModule } from '@angular/core';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { authInterceptor } from './interceptors/auth.interceptor';

@NgModule({
  providers: [
    provideHttpClient(withInterceptors([authInterceptor]))
  ]
})
export class AppModule {}
À retenir : L'ordre dans le tableau withInterceptors([]) définit l'ordre d'exécution. Le premier intercepteur est le premier à traiter la requête sortante et le dernier à traiter la réponse entrante.

Injection de dépendances avec inject()

Sans constructeur, l'injection se fait directement dans le corps de la fonction avec inject(). Ceci fonctionne car les intercepteurs s'exécutent dans un contexte d'injection.

import { inject } from '@angular/core';
import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';
import { catchError, throwError } from 'rxjs';
import { Router } from '@angular/router';
import { AuthService } from '../services/auth.service';

export const errorInterceptor: HttpInterceptorFn = (req, next) => {
  const router = inject(Router);
  const authService = inject(AuthService);

  return next(req).pipe(
    catchError((error: HttpErrorResponse) => {
      if (error.status === 401) {
        authService.logout();
        router.navigate(['/login']);
      }
      if (error.status === 403) {
        router.navigate(['/forbidden']);
      }
      return throwError(() => error);
    })
  );
};
inject() et HttpClient : Contrairement à l'ancien style, injecter HttpClient dans un intercepteur fonctionnel ne crée pas de dépendance circulaire. C'est l'un des grands avantages de cette approche.

Cas d'usage courants

1. Intercepteur de logging :

import { HttpInterceptorFn, HttpResponse } from '@angular/common/http';
import { tap } from 'rxjs';

export const loggingInterceptor: HttpInterceptorFn = (req, next) => {
  const start = Date.now();
  console.info(`[HTTP] ${req.method} ${req.url}`);

  return next(req).pipe(
    tap(event => {
      if (event instanceof HttpResponse) {
        console.info(`[HTTP] ${req.url} — ${Date.now() - start}ms`);
      }
    })
  );
};

2. Intercepteur avec token refresh (HttpClient sans circular dependency) :

import { inject } from '@angular/core';
import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';
import { catchError, switchMap, throwError } from 'rxjs';
import { AuthService } from '../services/auth.service';

export const tokenRefreshInterceptor: HttpInterceptorFn = (req, next) => {
  const authService = inject(AuthService);

  return next(req).pipe(
    catchError((error: HttpErrorResponse) => {
      if (error.status === 401 && !req.url.includes('/auth/refresh')) {
        return authService.refreshToken().pipe(
          switchMap(newToken => {
            const retryReq = req.clone({
              setHeaders: { Authorization: `Bearer ${newToken}` }
            });
            return next(retryReq);
          })
        );
      }
      return throwError(() => error);
    })
  );
};

3. Intercepteur conditionnel (skip sur certaines URLs) :

export const authInterceptor: HttpInterceptorFn = (req, next) => {
  // Ne pas ajouter le token sur les requêtes publiques
  if (req.url.includes('/public') || req.url.includes('/auth')) {
    return next(req);
  }

  const token = localStorage.getItem('token') ?? '';
  return next(req.clone({
    setHeaders: { Authorization: `Bearer ${token}` }
  }));
};

Comparaison classe vs fonctionnel

Ancien style (classe) :

// auth.interceptor.ts — ancien style
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor(private authService: AuthService) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const token = this.authService.getToken();
    return next.handle(req.clone({
      setHeaders: { Authorization: `Bearer ${token}` }
    }));
  }
}

// app.module.ts — enregistrement verbose
providers: [
  { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
]

Nouveau style (fonctionnel) :

// auth.interceptor.ts — nouveau style
export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const token = inject(AuthService).getToken();
  return next(req.clone({
    setHeaders: { Authorization: `Bearer ${token}` }
  }));
};

// app.config.ts — enregistrement concis
provideHttpClient(withInterceptors([authInterceptor]))
À retenir : Le style fonctionnel réduit le boilerplate de ~50%. Une classe de 15 lignes + enregistrement devient une fonction de 5 lignes.

Migrer d'un intercepteur classe

La migration est simple et peut se faire progressivement. Les deux styles coexistent : vous pouvez utiliser withInterceptors() et withInterceptorsFromDi() simultanément.

// Coexistence pendant la migration
provideHttpClient(
  withInterceptors([authInterceptor]),      // nouveaux
  withInterceptorsFromDi()                  // anciens (HTTP_INTERCEPTORS)
)

Étapes de migration :

  1. Créer la nouvelle fonction HttpInterceptorFn en parallèle
  2. Remplacer this.service par inject(Service)
  3. Remplacer next.handle(req) par next(req)
  4. Ajouter la fonction dans withInterceptors([])
  5. Retirer l'ancienne classe de HTTP_INTERCEPTORS
  6. Supprimer la classe et son décorateur @Injectable()
Compatibilité : HttpInterceptorFn est disponible depuis Angular 15. Les deux styles fonctionnent jusqu'à Angular 20 inclus — la classe n'est pas dépréciée mais le style fonctionnel est officiellement recommandé.