Guards & Resolvers
Route Guards
Section intitulée « Route Guards »Protect routes from unauthorized access.
CanActivate
Section intitulée « CanActivate »import { inject } from '@angular/core';import { Router, CanActivateFn } from '@angular/router';import { AuthService } from './auth.service';
export const authGuard: CanActivateFn = (route, state) => { const authService = inject(AuthService); const router = inject(Router);
if (authService.isLoggedIn()) { return true; }
// Rediriger vers login return router.createUrlTree(['/login']);};import { Routes } from '@angular/router';import { authGuard } from './auth.guard';
export const routes: Routes = [ { path: 'admin', component: AdminComponent, canActivate: [authGuard] // ← Guard applied here }, { path: 'login', component: LoginComponent }];import { Injectable, signal } from '@angular/core';
@Injectable({ providedIn: 'root' })export class AuthService { private loggedIn = signal(false);
isLoggedIn(): boolean { return this.loggedIn(); }
login() { this.loggedIn.set(true); }
logout() { this.loggedIn.set(false); }}CanActivateChild
Section intitulée « CanActivateChild »export const adminGuard: CanActivateFn = (route, state) => { const authService = inject(AuthService); return authService.hasRole('admin');};export const routes: Routes = [ { path: 'admin', canActivateChild: [adminGuard], // ← Appliqué à tous les enfants children: [ { path: 'users', component: UsersComponent }, { path: 'settings', component: SettingsComponent } ] }];CanDeactivate
Section intitulée « CanDeactivate »export interface CanComponentDeactivate { canDeactivate: () => boolean | Observable<boolean>;}
export const canDeactivateGuard: CanDeactivateFn<CanComponentDeactivate> = ( component) => { return component.canDeactivate ? component.canDeactivate() : true;};import { Component } from '@angular/core';
@Component({ selector: 'app-form', standalone: true, template: ` <form> <input [(ngModel)]="data" (input)="hasUnsavedChanges = true"> <button (click)="save()">Save</button> </form> `})export class FormComponent implements CanComponentDeactivate { data = ''; hasUnsavedChanges = false;
canDeactivate(): boolean { if (this.hasUnsavedChanges) { return confirm('You have unsaved changes. Leave anyway?'); } return true; }
save() { this.hasUnsavedChanges = false; // Save logic }}{ path: 'edit', component: FormComponent, canDeactivate: [canDeactivateGuard]}CanMatch
Section intitulée « CanMatch »export const featureGuard: CanMatchFn = (route, segments) => { const featureService = inject(FeatureService); return featureService.isFeatureEnabled('newFeature');};export const routes: Routes = [ { path: 'feature', canMatch: [featureGuard], // ← If true, use this route loadComponent: () => import('./new-feature.component') .then(m => m.NewFeatureComponent) }, { path: 'feature', // ← Fallback if canMatch fails loadComponent: () => import('./old-feature.component') .then(m => m.OldFeatureComponent) }];Resolvers
Section intitulée « Resolvers »Pré-charger les données avant d’activer la route.
Resolver de Base
Section intitulée « Resolver de Base »import { inject } from '@angular/core';import { ResolveFn } from '@angular/router';import { UserService } from './user.service';
export const userResolver: ResolveFn<User> = (route, state) => { const userService = inject(UserService); const id = route.paramMap.get('id')!; return userService.getUser(id);};export const routes: Routes = [ { path: 'user/:id', component: UserDetailComponent, resolve: { user: userResolver } // ← Data available as 'user' }];import { Component, inject } from '@angular/core';import { ActivatedRoute } from '@angular/router';
@Component({ selector: 'app-user-detail', standalone: true, template: ` <h2>{{ user.name }}</h2> <p>{{ user.email }}</p> `})export class UserDetailComponent { route = inject(ActivatedRoute);
// Get resolved data from snapshot user = this.route.snapshot.data['user'] as User;
// Ou de manière réactive ngOnInit() { this.route.data.subscribe(data => { this.user = data['user']; }); }}Resolvers Multiples
Section intitulée « Resolvers Multiples »export const userResolver: ResolveFn<User> = (route) => { return inject(UserService).getUser(route.params['id']);};
export const postsResolver: ResolveFn<Post[]> = (route) => { return inject(PostService).getPosts(route.params['id']);};export const routes: Routes = [ { path: 'user/:id', component: UserProfileComponent, resolve: { user: userResolver, // ← Available as data['user'] posts: postsResolver // ← Available as data['posts'] } }];@Component({ selector: 'app-user-profile', standalone: true, template: ` <h2>{{ user.name }}</h2> <div *ngFor="let post of posts"> <p>{{ post.title }}</p> </div> `})export class UserProfileComponent { route = inject(ActivatedRoute);
ngOnInit() { const data = this.route.snapshot.data; this.user = data['user']; this.posts = data['posts']; }}Gestion des Erreurs dans Resolver
Section intitulée « Gestion des Erreurs dans Resolver »import { catchError, EMPTY } from 'rxjs';
export const userResolver: ResolveFn<User> = (route) => { const userService = inject(UserService); const router = inject(Router);
return userService.getUser(route.params['id']).pipe( catchError(error => { console.error('Failed to load user', error); router.navigate(['/not-found']); return EMPTY; // Ne pas naviguer vers la route }) );};{ path: 'user/:id', component: UserDetailComponent, resolve: { user: userResolver } // ← Will redirect on error}Combiner Guards et Resolvers
Section intitulée « Combiner Guards et Resolvers »export const authGuard: CanActivateFn = (route, state) => { return inject(AuthService).isLoggedIn();};
export const unsavedChangesGuard: CanDeactivateFn<FormComponent> = (component) => { return component.canDeactivate();};export const userResolver: ResolveFn<User> = (route) => { return inject(UserService).getUser(route.params['id']);};
export const settingsResolver: ResolveFn<Settings> = (route) => { return inject(SettingsService).getSettings();};export const routes: Routes = [ { path: 'profile/:id', component: ProfileComponent, canActivate: [authGuard], // ← Check auth first canDeactivate: [unsavedChangesGuard], // ← Check before leaving resolve: { // ← Load data user: userResolver, settings: settingsResolver } }];Guard Basé sur les Rôles
Section intitulée « Guard Basé sur les Rôles »export function roleGuard(roles: string[]): CanActivateFn { return (route, state) => { const authService = inject(AuthService); const router = inject(Router);
const userRoles = authService.getUserRoles(); const hasRole = roles.some(role => userRoles.includes(role));
if (hasRole) { return true; }
return router.createUrlTree(['/unauthorized']); };}export const routes: Routes = [ { path: 'admin', component: AdminComponent, canActivate: [roleGuard(['admin', 'superuser'])] // ← Pass roles }, { path: 'moderator', component: ModeratorComponent, canActivate: [roleGuard(['moderator', 'admin'])] }];Guard Asynchrone
Section intitulée « Guard Asynchrone »export const permissionGuard: CanActivateFn = (route, state) => { const permissionService = inject(PermissionService); const router = inject(Router);
return permissionService.checkPermission(route.data['permission']).pipe( map(hasPermission => { if (hasPermission) { return true; } return router.createUrlTree(['/forbidden']); }) );};{ path: 'admin', component: AdminComponent, canActivate: [permissionGuard], data: { permission: 'admin.access' } // ← Passé au guard}Guard avec Redirection
Section intitulée « Guard avec Redirection »export const loginGuard: CanActivateFn = (route, state) => { const authService = inject(AuthService); const router = inject(Router);
if (authService.isLoggedIn()) { return true; }
// Rediriger vers login avec l'URL de retour return router.createUrlTree(['/login'], { queryParams: { returnUrl: state.url } });};@Component({ selector: 'app-login', standalone: true, template: ` <button (click)="login()">Login</button> `})export class LoginComponent { route = inject(ActivatedRoute); router = inject(Router); authService = inject(AuthService);
login() { this.authService.login();
// Rediriger vers l'URL d'origine const returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/'; this.router.navigateByUrl(returnUrl); }}Bonnes Pratiques
Section intitulée « Bonnes Pratiques »- Utiliser les guards pour l’authentification/autorisation
- Utiliser les resolvers pour pré-charger les données critiques
- Toujours gérer les erreurs dans les resolvers
- Retourner
UrlTreeau lieu derouter.navigate()dans les guards - Garder les guards simples et ciblés
- Utiliser
canMatchpour les feature flags - Utiliser
canDeactivatepour les changements non sauvegardés - Combiner plusieurs guards si nécessaire
- Considérer les états de chargement pour les resolvers

