Angular Guards : protéger les routes de votre application

🏷️ Front-end 📅 12/04/2026 22:00:00 👤 Mezgani said
Angular Guards Routing Securite
Angular Guards : protéger les routes de votre application

Maîtriser les guards Angular pour sécuriser les routes, contrôler les accès et gérer les redirections avec canActivate, canDeactivate et resolve.

Qu'est-ce qu'un guard Angular ?

Un guard est un service Angular qui contrôle l'accès aux routes. Il s'exécute avant la navigation et peut autoriser, bloquer ou rediriger l'utilisateur. Angular propose plusieurs types de guards selon le besoin :

  • canActivate — vérifie si l'utilisateur peut accéder à une route.
  • canDeactivate — vérifie si l'utilisateur peut quitter une route (ex : formulaire non sauvegardé).
  • canActivateChild — protège toutes les routes enfants d'un module.
  • resolve — charge des données avant l'affichage du composant.
  • canLoad — empêche le chargement lazy d'un module si non autorisé.
Angular 15+ : les guards sont désormais de simples fonctions (functional guards) plutôt que des classes. La syntaxe est plus courte et ne nécessite plus d'injection de service dans un @Injectable.

canActivate : protéger une route

Le guard canActivate est le plus courant. Il protège une route en vérifiant une condition — typiquement si l'utilisateur est connecté.

Syntaxe fonctionnelle (Angular 15+) :

// auth.guard.ts
import { inject } from '@angular/core';
import { Router, CanActivateFn } from '@angular/router';
import { AuthService } from './auth.service';

export const authGuard: CanActivateFn = () => {
    const auth   = inject(AuthService);
    const router = inject(Router);

    if (auth.isLoggedIn()) {
        return true;
    }

    // Rediriger vers la page de connexion
    return router.createUrlTree(['/login']);
};

Appliquer le guard dans le routing :

// app.routes.ts
import { Routes } from '@angular/router';
import { authGuard } from './auth.guard';
import { DashboardComponent } from './dashboard/dashboard.component';

export const routes: Routes = [
    {
        path: 'dashboard',
        component: DashboardComponent,
        canActivate: [authGuard]   // Guard appliqué ici
    },
    {
        path: 'login',
        component: LoginComponent
    }
];

AuthService minimal

// auth.service.ts
import { Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class AuthService {
    isLoggedIn(): boolean {
        return !!localStorage.getItem('token');
    }
}

Guard avec rôle (vérification admin) :

export const adminGuard: CanActivateFn = () => {
    const auth   = inject(AuthService);
    const router = inject(Router);

    if (auth.isLoggedIn() && auth.hasRole('admin')) {
        return true;
    }

    return router.createUrlTree(['/unauthorized']);
};

canDeactivate : empêcher de quitter une page

canDeactivate s'exécute quand l'utilisateur tente de quitter une route. Utile pour protéger un formulaire avec des modifications non sauvegardées.

// unsaved-changes.guard.ts
import { CanDeactivateFn } from '@angular/router';

export interface HasUnsavedChanges {
    hasUnsavedChanges(): boolean;
}

export const unsavedChangesGuard: CanDeactivateFn<HasUnsavedChanges> = (component) => {
    if (component.hasUnsavedChanges()) {
        return confirm('Vous avez des modifications non sauvegardées. Quitter quand même ?');
    }
    return true;
};

Implémenter l'interface dans le composant :

// edit-article.component.ts
import { Component } from '@angular/core';
import { HasUnsavedChanges } from './unsaved-changes.guard';

@Component({ ... })
export class EditArticleComponent implements HasUnsavedChanges {
    isDirty = false;

    hasUnsavedChanges(): boolean {
        return this.isDirty;
    }

    onFormChange() {
        this.isDirty = true;
    }

    onSave() {
        // ... sauvegarde
        this.isDirty = false;
    }
}

Application dans les routes :

export const routes: Routes = [
    {
        path: 'articles/:id/edit',
        component: EditArticleComponent,
        canDeactivate: [unsavedChangesGuard]
    }
];
A retenir : au lieu d'un confirm() natif, utilisez une modale Angular pour une meilleure UX. Retournez un Observable<boolean> depuis le guard pour attendre la réponse de l'utilisateur.

canActivateChild : protéger les routes enfants

canActivateChild s'applique à toutes les routes enfants d'un groupe. Pratique pour protéger une section entière de l'application (ex : espace admin) en un seul endroit.

// admin.guard.ts
import { inject } from '@angular/core';
import { CanActivateChildFn, Router } from '@angular/router';
import { AuthService } from './auth.service';

export const adminChildGuard: CanActivateChildFn = () => {
    const auth   = inject(AuthService);
    const router = inject(Router);

    return auth.hasRole('admin')
        ? true
        : router.createUrlTree(['/unauthorized']);
};

Dans le routing, le guard protège automatiquement toutes les routes filles :

export const routes: Routes = [
    {
        path: 'admin',
        canActivateChild: [adminChildGuard],  // Protège TOUTES les routes enfants
        children: [
            { path: 'users',    component: AdminUsersComponent },
            { path: 'articles', component: AdminArticlesComponent },
            { path: 'settings', component: AdminSettingsComponent }
        ]
    }
];
Avantage : un seul guard protège l'ensemble des routes enfants. Plus besoin de répéter canActivate sur chaque route individuelle.

resolve : pré-charger des données avant l'affichage

Le resolve guard charge des données depuis une API avant que le composant ne s'affiche. Le composant reçoit les données déjà disponibles via ActivatedRoute, sans état de chargement à gérer.

// article.resolver.ts
import { inject } from '@angular/core';
import { ResolveFn, Router } from '@angular/router';
import { ArticleService } from './article.service';
import { Article } from './article.model';
import { catchError, EMPTY } from 'rxjs';

export const articleResolver: ResolveFn<Article> = (route) => {
    const articleService = inject(ArticleService);
    const router         = inject(Router);
    const id             = route.paramMap.get('id')!;

    return articleService.getById(id).pipe(
        catchError(() => {
            // Rediriger si l'article n'existe pas
            router.navigate(['/404']);
            return EMPTY;
        })
    );
};

Appliquer le resolver sur la route :

export const routes: Routes = [
    {
        path: 'articles/:id',
        component: ArticleDetailComponent,
        resolve: { article: articleResolver }  // Clé "article" disponible dans le composant
    }
];

Récupérer les données dans le composant :

// article-detail.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Article } from './article.model';

@Component({ ... })
export class ArticleDetailComponent implements OnInit {
    article!: Article;

    constructor(private route: ActivatedRoute) {}

    ngOnInit() {
        // Données déjà chargées, pas de loader nécessaire
        this.article = this.route.snapshot.data['article'];
    }
}
A retenir : avec resolve, la navigation est retardée jusqu'à ce que les données soient disponibles. Le composant s'affiche directement avec ses données, ce qui évite les états de chargement complexes.

Bonnes pratiques production

  • Préférer les functional guards (Angular 15+) aux classes — moins de boilerplate, plus testables.
  • Toujours retourner router.createUrlTree() plutôt que router.navigate() dans un guard — évite des effets de bord.
  • Ne jamais exposer des données sensibles côté client — le guard est un filtre UX, pas une sécurité serveur.
  • Combiner plusieurs guards sur une route : canActivate: [authGuard, adminGuard].
  • Utiliser canLoad (ou canMatch depuis Angular 15) pour les modules lazy-loaded afin d'éviter de télécharger le code inutilement.
  • Tester les guards unitairement avec TestBed en mockant les services injectés.
// Combiner auth + rôle sur une même route
export const routes: Routes = [
    {
        path: 'admin',
        component: AdminComponent,
        canActivate: [authGuard, adminGuard],
        canActivateChild: [adminChildGuard],
        children: [
            {
                path: 'articles/:id/edit',
                component: EditArticleComponent,
                canDeactivate: [unsavedChangesGuard],
                resolve: { article: articleResolver }
            }
        ]
    }
];

canMatch vs canLoad (Angular 15+)

  1. canLoad est déprécié depuis Angular 15.
  2. Utiliser canMatch à la place — fonctionne avec les routes lazy et les standalone components.
  3. canMatch évalue si une route correspond, avant même de charger le module.