La Gestion d'Erreurs Centralisée dans Angular 17

Orbit TurnerOrbit Turner
9 min read

Hello Orbiters ! Je me suis embarqué dans le développement d'un projet SaaS passionnant, utilisant Angular 17, AppWrite, Python et OpenAI. En parallèle, je souhaite partager avec vous les coulisses de ce projet, y compris la structure de mon code et les techniques que j'adopte pour garantir la robustesse de l'application.

À travers cette série d'articles, je vous partagerai des insights, des astuces et les décisions clés que je prends en cours de route.

🚀 Aujourd'hui, je plonge dans un sujet crucial mais souvent négligé : la gestion des erreurs dans les applications Angular. Lorsque vous construisez une application SaaS, anticiper et gérer correctement les erreurs peut faire la différence entre un produit "ok" et un produit exceptionnel. Alors, voici comment j'ai adopté une stratégie de gestion des erreurs dans ce projet.

La Structure : un Petit Tour du Propriétaire

Mon projet Angular est bien organisé (je l'espère !), avec des composants, des services, des intercepteurs HTTP et bien sûr, des gestionnaires d'erreurs personnalisés. La cerise sur le gâteau est notre gestion d'erreur globale via deux fichiers clés : orbit-error-handler.helper.ts et http-error-handler.helper.ts.

┣━━ 📂 src
┃   ┣━━ 📂 app
┃   ┃   ┣━━ 📂 components
┃   ┃   ┃   ┣━━ 📂 layouts
┃   ┃   ┃   ┃   ┣━━ 📂 main-footer
┃   ┃   ┃   ┃   ┣━━ 📂 main-header
┃   ┃   ┃   ┃   ┣━━ 📂 main-layout
┃   ┃   ┃   ┃   ┗━━ 📂 main-sidebar
┃   ┃   ┃   ┣━━ 📂 ui-elements
┃   ┃   ┃   ┗━━ 📂 views
┃   ┃   ┃       ┣━━ 📂 auth
┃   ┃   ┃       ┃   ┣━━ 📂 forgotten-password
┃   ┃   ┃       ┃   ┣━━ 📂 login
┃   ┃   ┃       ┃   ┣━━ 📂 reset-password
┃   ┃   ┃       ┃   ┗━━ 📂 signup
┃   ┃   ┃       ┗━━ 📂 tenders
┃   ┃   ┃           ┗━━ 📂 offres-list-view
┃   ┃   ┣━━ 📂 core
┃   ┃   ┃   ┣━━ 📂 configs
┃   ┃   ┃   ┃   ┣━━ 📂 routes
┃   ┃   ┃   ┃   ┃   ┣━━ 📄 app.routes.ts
┃   ┃   ┃   ┃   ┃   ┗━━ 📄 routes.types.ts
┃   ┃   ┃   ┃   ┣━━ 📂 seo
┃   ┃   ┃   ┃   ┃   ┣━━ 📄 page-descriptions.seo.ts
┃   ┃   ┃   ┃   ┃   ┣━━ 📄 page-titles.seo.ts
┃   ┃   ┃   ┃   ┃   ┗━━ 📄 seo.config.ts
┃   ┃   ┃   ┃   ┗━━ 📄 app.config.ts
┃   ┃   ┃   ┣━━ 📂 errors
┃   ┃   ┃   ┃   ┣━━ 📄 http-error-handler.helper.ts
┃   ┃   ┃   ┃   ┗━━ 📄 orbit-error-handler.helper.ts
┃   ┃   ┃   ┣━━ 📂 lang
┃   ┃   ┃   ┃   ┣━━ 📄 messages.en.ts
┃   ┃   ┃   ┃   ┗━━ 📄 messages.fr.ts
┃   ┃   ┃   ┣━━ 📂 libs
┃   ┃   ┃   ┃   ┗━━ 📄 appwrite.ts
┃   ┃   ┃   ┣━━ 📂 security
┃   ┃   ┃   ┗━━ 📂 services
┃   ┃   ┃       ┗━━ 📂 auth
┃   ┃   ┃           ┣━━ 📄 auth.service.spec.ts
┃   ┃   ┃           ┣━━ 📄 auth.service.ts
┃   ┃   ┃           ┗━━ 📄 auth.types.ts
┃   ┃   ┣━━ 📂 helpers
┃   ┃   ┃   ┣━━ 📂 alerts
┃   ┃   ┃   ┃   ┣━━ 📄 alert-helper.service.spec.ts
┃   ┃   ┃   ┃   ┣━━ 📄 alert-helper.service.ts
┃   ┃   ┃   ┃   ┗━━ 📄 alert.types.ts
┃   ┃   ┃   ┣━━ 📂 translator
┃   ┃   ┃   ┃   ┣━━ 📄 translation-helper.service.spec.ts
┃   ┃   ┃   ┃   ┗━━ 📄 translation-helper.service.ts
┃   ┃   ┃   ┗━━ 📄 miscs.helper.ts
┃   ┃   ┣━━ 📄 app.component.html
┃   ┃   ┣━━ 📄 app.component.scss
┃   ┃   ┣━━ 📄 app.component.spec.ts
┃   ┃   ┗━━ 📄 app.component.ts
┃   ┣━━ 📂 environments
┃   ┃   ┣━━ 📄 environment.development.ts
┃   ┃   ┣━━ 📄 environment.example.ts
┃   ┃   ┗━━ 📄 environment.ts
┃   ┣━━ 📂 locales
┃   ┃   ┣━━ 📄 messages.en.xlf
┃   ┃   ┗━━ 📄 messages.xlf
┃   ┣━━ 📂 assets
┃   ┣━━ 📄 favicon.ico
┃   ┣━━ 📄 index.html
┃   ┣━━ 📄 main.ts
┃   ┗━━ 📄 styles.scss
┣━━ 📄 angular.json
┣━━ 📄 compose.yaml
┣━━ 📄 DevLog-Plateforme-AOPS-Front.md
┣━━ 📄 Dockerfile
┣━━ 📄 LICENSE
┣━━ 📄 package.json
┣━━ 📄 README.Docker.md
┣━━ 📄 README.md
┣━━ 📄 step-logs.orbit
┣━━ 📄 tsconfig.app.json
┣━━ 📄 tsconfig.json
┣━━ 📄 tsconfig.spec.json
┣━━ 📄 yarn-error.log
┗━━ 📄 yarn.lock

Le Gestionnaire d'Erreurs Orbital : OrbitErrorHandler

Pourquoi ce Nom ?

Parce que nous voulons que nos erreurs soient gérées avec autant de précision que la trajectoire d'un satellite en orbite ! 😄

Non je blague je suis juste un gros narcissique qui trouve que ce nom est PARFAIT 😫.

Code et Explication

Le OrbitErrorHandler est un service injectable qui intercepte toutes les erreurs non capturées dans l'application. Il utilise NGXLogger pour loguer les erreurs, enrichies de contextes tels que l'URL courante et d'autres informations pertinentes que vous pouvez ajouter.

import { ErrorHandler, Injectable, inject } from '@angular/core';
import { NGXLogger } from "ngx-logger";
import { environment } from '../../../environments/environment';
import { AlertHelperService } from '../../helpers/alerts/alert-helper.service';
import { isDebugMode } from '../configs/app.config';
import { Router } from '@angular/router';
import { TranslationHelperService } from '../../helpers/translator/translation-helper.service';


@Injectable({
  providedIn: 'root'
})
export class OrbitErrorHandler implements ErrorHandler {
  // ===========[ DEPENDENCIES INJECTIONS ]===========
  private router = inject(Router);
  private translationService = inject(TranslationHelperService);
  private logger = inject(NGXLogger);
  private alert = inject(AlertHelperService);


  // ===========[ METHODS ]===========
  handleError(error: unknown, context?: unknown): void {

    this.logger.error(error);
    const errorContext = {
      route: this.router.url,
      callerGivenContext: context,
      // TODO: Add any other context information here
    };

    if (environment.production && !isDebugMode()) {
      this.alert.error(`> ${this.translationService.translate('errors.generic')}`);
      // TODO: Consider sending the error details to an error tracking service here
    } else {
      this.logger.error(`> Error context:`, errorContext);
      this.alert.error(`> ${this.translationService.translate('errors.verboseGenericError')} ${error}`);
    }
  }
}

Pourquoi est-ce Puissant ?

L'idée est de ne pas juste "attraper" les erreurs, mais de fournir un contexte qui aide à diagnostiquer le problème. En production, nous gardons les messages d'erreur génériques pour l'utilisateur mais riches en détails pour le développeur.

Mais aussi il permet de pouvoir intégrer dans le futur un systeme de tracking des erreurs du type Sentry, logRocket etc.

Le Filtrage HTTP : AopsHttpErrorInterceptor

Le Job de cet Intercepteur

Cet intercepteur s'attaque aux erreurs HTTP en interceptant les réponses des requêtes avant qu'elles n'atteignent vos services ou vos composants. Il distingue les différents types d'erreurs (401, 403, 404, 500) et personnalise les messages d'erreur en fonction.

Code et Explication

import { OrbitErrorHandler } from './orbit-error-handler.helper';
import { Injectable, inject } from '@angular/core';
import {
    HttpInterceptor,
    HttpRequest,
    HttpHandler,
    HttpEvent,
    HttpErrorResponse
} from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { environment } from '../../../environments/environment';
import { AlertHelperService } from '../../helpers/alerts/alert-helper.service';

@Injectable()
export class AopsHttpErrorInterceptor implements HttpInterceptor {
    private errorHandler = inject(OrbitErrorHandler);
    private alertService = inject(AlertHelperService);

    intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
        return next.handle(request).pipe(
            catchError((error: HttpErrorResponse) => {
                let errorMessage = 'Une erreur est survenue. Veuillez réessayer.';

                switch (error.status) {
                    case 401: errorMessage = 'Vous n\'êtes pas autorisé à accéder à cette ressource.';
                              break;
                    case 403: errorMessage = 'Accès interdit.';
                              break;
                    case 404: errorMessage = 'La ressource demandée est introuvable.';
                              break;
                    case 500: errorMessage = 'Erreur interne du serveur. Veuillez réessayer plus tard.';
                              break;
                    default
                    break;
                }

                if (environment.production && !isDebugMode()) {
                    this.alertService.error(errorMessage);
                }

                this.errorHandler.handleError(error);

                return throwError(() => error);
            })
        );
    }
}

Pourquoi C'est Top ?

Cet intercepteur centralise la gestion des erreurs HTTP, ce qui facilite leur gestion et assure que toutes les erreurs sont traitées de manière cohérente. En outre, il permet de déléguer les erreurs spécifiques au OrbitErrorHandler, où elles peuvent être loguées et traitées plus en détail.

Intégration Globale avec app.config.ts

Pour que ces handlers soient vraiment efficaces, ils doivent être intégrés globalement. Cela se fait dans le app.config.ts où nous configurons notre application pour utiliser ces gestionnaires comme fournisseurs globaux.

Code et Points Clés

import {
  APP_INITIALIZER,
  ApplicationConfig,
  ErrorHandler,
  LOCALE_ID,
  importProvidersFrom,
} from "@angular/core";
import {
  Routes,
  provideRouter,
  withComponentInputBinding,
} from "@angular/router";
import { HTTP_INTERCEPTORS, provideHttpClient } from "@angular/common/http";
import { OrbitErrorHandler } from "../errors/orbit-error-handler.helper";
import { AopsHttpErrorInterceptor } from "../errors/http-error-handler.helper";
import {
  TranslationHelperService,
  getPreferredLanguage,
} from "../../helpers/translator/translation-helper.service";

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes as Routes, withComponentInputBinding()),
    provideHttpClient(),
    { provide: ErrorHandler, useClass: OrbitErrorHandler },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AopsHttpErrorInterceptor,
      multi: true,
    },
    {
      provide: APP_INITIALIZER,
      useFactory: (translationService: TranslationHelperService) => () =>
        translationService.loadTranslations(getPreferredLanguage()),
      deps: [TranslationHelperService],
      multi: true,
    },
  ],
};

Pourquoi C'est Crucial ?

Intégrer ces gestionnaires et intercepteurs globalement garantit qu'ils seront utilisés à travers toute l'application, offrant une cohérence et une couverture d'erreur global.

Use Case

Pour illustrer comment utiliser OrbitErrorHandler dans un composant spécifique d'Angular, nous allons créer un exemple pratique où ce gestionnaire d'erreurs est utilisé dans un composant de gestion des utilisateurs. Le but est de montrer comment vous pouvez capturer et traiter les erreurs qui se produisent lors des interactions avec une API utilisateur.

Scenario: Gestion des Erreurs dans un Composant de Gestion des Utilisateurs

Supposons que nous avons un composant UserManagerComponent qui s'occupe des opérations telles que la récupération, la création et la suppression des utilisateurs. Voici un exemple simplifié de ce composant :

import { Component, OnInit, OnDestroy, inject } from "@angular/core";
import { Subscription } from "rxjs";
import { UserService } from "../services/user.service";
import { NGXLogger } from "ngx-logger";
import { OrbitErrorHandler } from "../errors/orbit-error-handler.helper";

@Component({
  selector: "app-user-manager",
  templateUrl: "./user-manager.component.html",
  styleUrls: ["./user-manager.component.scss"],
})
export class UserManagerComponent implements OnInit, OnDestroy {
  // ===========[ DEPENDENCIES INJECTIONS ]===========
  private userService = inject(UserService);
  private logger = inject(NGXLogger);
  private errorHandler = inject(OrbitErrorHandler);
  private subscriptions: Subscription = new Subscription();

  // ===========[ INITIALIZATION ]===========
  ngOnInit() {
    this.loadUsers();
  }

  // ===========[ METHODS ]===========
  loadUsers() {
    const subscription = this.userService.getUsers().subscribe({
      next: (users) => {
        this.logger.info("Users loaded successfully", users);
      },
      error: (err) => {
        this.errorHandler.handleError(err, {
          component: "UserManagerComponent",
          function: "loadUsers",
        });
      },
    });
    this.subscriptions.add(subscription);
  }

  addUser(user) {
    const subscription = this.userService.addUser(user).subscribe({
      next: (newUser) => {
        this.logger.info("User added successfully", newUser);
      },
      error: (err) => {
        this.errorHandler.handleError(err, {
          component: "UserManagerComponent",
          function: "addUser",
        });
      },
    });
    this.subscriptions.add(subscription);
  }

  deleteUser(userId) {
    const subscription = this.userService.deleteUser(userId).subscribe({
      next: () => {
        this.logger.info("User deleted successfully");
      },
      error: (err) => {
        this.errorHandler.handleError(err, {
          component: "UserManagerComponent",
          function: "deleteUser",
        });
      },
    });
    this.subscriptions.add(subscription);
  }

  // ===========[ CLEANUP ]===========
  ngOnDestroy() {
    this.subscriptions.unsubscribe();
  }
}

Explication

Dans ce composant, OrbitErrorHandler est injecté via le helper d'injection (inject) ou le constructeur (si vous préférez), ce qui nous permet de l'utiliser pour gérer les erreurs de manière uniforme à travers le composant. Chaque méthode qui interagit avec le UserService (pour charger, ajouter ou supprimer des utilisateurs) a une gestion d'erreur où OrbitErrorHandler est appelé si une erreur se produit.

  • Injectabilité: OrbitErrorHandler est injecté dans le composant, permettant une utilisation flexible et réutilisable à travers l'application.
  • Personnalisation des erreurs: Chaque appel de la méthode handleError inclut un contexte spécifique, qui pourrait être logué ou utilisé pour afficher des messages d'erreur plus détaillés à l'utilisateur ou dans les logs.
  • Centralisation de la gestion des erreurs: En utilisant OrbitErrorHandler dans les composants, vous centralisez la gestion des erreurs, ce qui facilite la maintenance et l'évolution du code.

Ce cas d'utilisation montre comment OrbitErrorHandler peut être intégré dans les composants Angular pour gérer efficacement les erreurs, offrant ainsi une meilleure expérience utilisateur et une meilleure capacité de diagnostic pour les développeurs.

En Conclusion

Adopter une stratégie proactive de gestion des erreurs dans vos projets Angular n'est pas juste une bonne pratique ; c'est une nécessité pour assurer la stabilité et la fiabilité de votre application. Avec des outils comme OrbitErrorHandler et AopsHttpErrorInterceptor, vous êtes bien équipé pour non seulement traiter les erreurs efficacement, mais aussi pour les diagnostiquer de manière approfondie, ce qui est essentiel pour les résoudre rapidement.

Rendez-vous dans le prochain article où je plongerai dans un autre aspect fascinant de mon projet Angular.

Si vous avez des questions ou souhaitez aborder des aspects spécifiques pour les prochains articles, n'hésitez pas à me le faire savoir!

Restez à l'écoute et codez prudemment! 🌟


Suivez-moi !

Website: Mon Portfolio

Linkedin: Mon Linkedin

Github: Mon Github

Twitter: @OrbitTurner

Youtube: Ma Chaine

0
Subscribe to my newsletter

Read articles from Orbit Turner directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Orbit Turner
Orbit Turner

A Creative Dreamer who has always loved going out of the box to innovate. 🚀 I am Mohamed Gueye, better known by the pseudonym Orbit Turner, a technology, science, and art enthusiast since my early days. My journey is an exciting blend of autonomy, curiosity, and continuous learning.