← Retour au blog
Angularerror-handlinginterceptorsmonitoringresilience

Gestion des erreurs globale en Angular : implémenter un système robuste avec ErrorHandler et intercepteurs

Un projet Angular sans stratégie d'erreurs globale ressemble à un système sans filet : chaque composant gère ses propres erreurs, les logs se perdent, et l'expérience utilisateur devient imprévisible. La gestion centralisée élimine cette fragmentation en canalisant toutes les erreurs—HTTP, non-capturées, promise rejections—vers un point unique de traitement. Cela ne signifie pas ignorer les erreurs métier locales (validation de formulaire, confirmation utilisateur), mais plutôt construire une couche robuste qui empêche les bugs de passer inaperçus en production et offre une expérience cohérente.

Construire un ErrorHandler personnalisé

Le cœur d'une gestion d'erreurs globale repose sur l'implémentation d'un ErrorHandler personnalisé qui remplace le gestionnaire par défaut d'Angular. Ce service intercepte toutes les exceptions non capturées, y compris les erreurs synchrones et les rejets de promesses non gérés. Plutôt que simplement logger à la console, un ErrorHandler robuste classifie les erreurs (réseau, validation, serveur), les enrichit avec du contexte (URL actuelle, état utilisateur, version app) et les envoie à un service de monitoring centralisé. La clé est de différencier les erreurs attendues (erreur 404, timeout) des erreurs inattendues qui révèlent un bug réel.

Voici une implémentation concrète :

import { ErrorHandler, Injectable, Injector } from '@angular/core';

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

import { LoggingService } from './logging.service';

import { NotificationService } from './notification.service';

@Injectable()

export class GlobalErrorHandler implements ErrorHandler {

constructor(private injector: Injector) {}

handleError(error: Error | HttpErrorResponse): void {

const loggingService = this.injector.get(LoggingService);

const notificationService = this.injector.get(NotificationService);

let userMessage = 'Une erreur est survenue. Veuillez réessayer.';

let severity = 'error';

if (error instanceof HttpErrorResponse) {

if (error.status === 0) {

userMessage = 'Impossible de contacter le serveur. Vérifiez votre connexion.';

severity = 'warning';

} else if (error.status >= 500) {

userMessage = 'Le serveur rencontre un problème. Nous travaillons à le résoudre.';

severity = 'error';

} else if (error.status === 401) {

userMessage = 'Votre session a expiré. Reconnexion en cours...';

severity = 'info';

}

}

loggingService.logError({

message: error.message,

stack: error instanceof Error ? error.stack : undefined,

context: {

timestamp: new Date().toISOString(),

url: window.location.href,

userAgent: navigator.userAgent

}

});

notificationService.notify(userMessage, severity);

}

}```

L'utilisation d'Injector.get() plutôt que l'injection constructeur évite les dépendances circulaires : le service de logging et le service de notification ne doivent pas dépendre eux-mêmes du ErrorHandler. La classification des erreurs HTTP permet d'adapter le message à l'utilisateur sans révéler d'informations sensibles.

Intercepter et transformer les erreurs HTTP

Les erreurs HTTP méritent une attention particulière car elles sont prévisibles et souvent récupérables. Un HttpErrorInterceptor dédié capture les réponses d'erreur, applique des stratégies de retry intelligentes et les transforme en domaine métier avant qu'elles ne remontent au composant. Cette approche réduit la boilerplate : les composants n'ont plus besoin de gérer les cas 401 ou les timeouts temporaires.

import { Injectable } from '@angular/core';

import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpErrorResponse } from '@angular/common/http';

import { Observable, throwError, timer } from 'rxjs';

import { retry, retryWhen, mergeMap, finalize } from 'rxjs/operators';

@Injectable()

export class HttpErrorInterceptor implements HttpInterceptor {

intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {

return next.handle(request).pipe(

retryWhen(errors =>

errors.pipe(

mergeMap((error: HttpErrorResponse, index) => {

if (this.isRetryable(error) && index < 3) {

return timer(Math.pow(2, index) * 1000);

}

return throwError(() => error);

})

)

),

finalize(() => {

// cleanup ou logging

})

);

}

private isRetryable(error: HttpErrorResponse): boolean {

return (error.status === 0 || error.status >= 500) && error.status !== 501;

}

}```

Ce pattern applique un backoff exponentiel (1s, 2s, 4s) et ne retry que les erreurs réseau ou 5xx. Les 4xx (404, 403, 400) sont laissées intactes car rejouer la requête ne les résoudra pas. Le finalize() permet de nettoyer les ressources ou d'envoyer des métriques sans dépendre de la réussite ou l'échec.

Contexte riche et traçabilité

Une erreur sans contexte est difficile à déboguer. Un ErrorContext injectable enrichit chaque erreur avec des informations pertinentes : route actuelle, état utilisateur, version de l'app, paramètres de requête. Ce contexte doit être collecté au niveau du GlobalErrorHandler et transmis au système de monitoring (Sentry, LogRocket, etc.). Évitez de logger des données sensibles (tokens, mots de passe, numéros de carte) ; utilisez une sanitization stricte.

interface ErrorContext {

timestamp: string;

url: string;

userId?: string;

routePath: string;

appVersion: string;

requestId?: string;

userAgent: string;

}

loggingService.logError({

error,

context: {

...baseContext,

requestId: request.headers.get('x-request-id'),

userId: currentUser?.id

}

});```

L'ajout d'un requestId permet de corréler les erreurs côté client avec les logs serveur. Si votre infrastructure utilise OpenTelemetry ou un système de tracing distribué, propagez cet ID dans les headers pour une visibilité complète.

Pièges courants et bonnes pratiques

Un erreur fréquente consiste à logger deux fois la même erreur : une fois dans l'interceptor HTTP, une fois dans le GlobalErrorHandler. Résultat : des doublons dans vos logs de monitoring et du bruit qui masque les vrais bugs. Solution : laissez l'interceptor transformer et enrichir, mais laissez le GlobalErrorHandler être le seul point d'appel à loggingService.log(). Autre piège : ne pas tester votre gestion d'erreurs en staging. Les erreurs réseau, les timeouts et les rejets de promise sont rares en développement local. Configurez des outils comme Cypress ou Playwright pour simuler des réponses lentes (throttling) et des pannes réseau partielles. Enfin, méfiez-vous de masquer les erreurs avec un try-catch trop large : capturer Error et continuer silencieusement transforme un bug détectable en comportement erratique invisible.

Conclusion

Une gestion d'erreurs globale robuste en Angular repose sur trois piliers : un GlobalErrorHandler qui centralise la logique, un HttpErrorInterceptor qui gère les cas réseau avec retry intelligent, et un contexte riche qui facilite le debugging. Cette approche élimine la fragmentation, réduit la boilerplate et améliore la résilience. Commencez par implémenter le GlobalErrorHandler et l'interceptor HTTP, puis enrichissez progressivement le contexte selon vos besoins de monitoring. Testez agressivement en staging avec des conditions réseau dégradées. Un système d'erreurs bien pensé ne rend pas votre app infaillible, mais il vous permet de détecter et corriger les vrais problèmes rapidement, plutôt que de découvrir des bugs obscurs en production.

Développeur Angular & Mobile freelance — Strasbourg.

© 2026 Emilien Pons — Tous droits réservés.Conçu avec Angular, PrimeNG et ❤️