image auteur
Lucas BENTO-VERSACE 10min • 25-07-2023

Angular - Les guards

Angular - Les guards

Dans cet article vous apprendez ce que sont les guards dans le framework Angular et comment les utiliser.

Introduction

Dans une application traditionnelle où le backend génère la page à afficher, on vérifierait côté serveur l'autorisation d'accès aux pages et on renverrait une erreur 403 si l'utilisateur ne possède pas l'autorisation d'accès.

Dans une application utilisant un framework SPA comme Angular, toutes les pages sont détenues par l'utilisateur dès le premier chargement. Donc restreindre l'accès à certaines pages devient la responsabilité du front-end. Et pour gérer ces restrictions les développeurs du framework Angular ont créé : Les Router Guards.

Avec les Router Guards vous pouvez empêcher l'utilisateur d'accéder à certaines routes s'ils n'ont pas l'autorisation requise, ou de manière plus générale intercepter un changement de route pour y exécuter du code avant que la route ne se charge.

Type de guards

Voici la liste exhaustive des guards d'Angular :

  • CanActivate : Vérifie si un utilisateur peut accéder à une route
  • CanActivateChild : Vérifie si un utilisateur peut accéder a une route enfant
  • CanDeactivate : Vérifie si un utilisateur peut quitter une route
  • Resolve : Récupère les données d'une route avant l'activation d'une route
  • CanLoad : Vérifie si un utilisateur peut accéder à un module lazy-loading

Dans cet article, nous allons parler des 3 premiers types listés ci-dessus. Les resolvers et le guard CanLoad feront l'objet d'un article dédié.

Pour une même route, vous pouvez implémenter autant de guards que vous le souhaitez.

La CLI peut vous aider à générer un guard. Entrez cette commande à la racine de votre projet :

ng generate guard nameOfGuard

Puis sélectionnez le type de guard souhaité.

CanActivate

Les guards sont implémentés en tant que services et doivent être injectés. Ils disposent donc du décorateur @Injectable.

Les guards retournent soit true si la navigation et autorisée, soit false dans le cas contraire.

Vous pouvez également renvoyer un Observable ou une Promise émettant un booléen, si par exemple vous devez contacter une API.

Voici le code du fichier que la CLI génère :

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class TestGuard implements CanActivate {
  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    return true;
  }
}
your.guard.ts

Dans cet exemple, la fonction canActivate renvoie true dans tous les cas, donc le guard n'a aucun effet et laisse la navigation avoir lieu.

Il ne faudra pas oublier de founir le guard à votre module ! Via providedIn ou la liste de providers de votre module.

Ensuite, ajoutez le guard à une ou plusieurs routes via la propriété canActivate :

const routes: Routes = [
  {path: '', redirectTo: 'home', pathMatch: 'full'},
  {path: 'find', redirectTo: 'search'},
  {path: 'home', component: HomeComponent},
  {path: 'search', component: SearchComponent},
  {
    path: 'pokemon/:pokemonId',
    component: PokemonComponent,
    canActivate: [TestGuard], // <-- AJOUT DU GUARD
    children: [
      {path: '', redirectTo: 'Bio'},
      {path: 'attack', component: AttackComponent},
      {path: 'bio', component: BiographieComponent},
    ]
  },
  {path: '**', component: HomeComponent}
];
app-routing.module.ts

La route /pokemon/{pokemonId} ainsi que les routes enfant /pokemon/{pokemonId}/attack et /pokemon/{pokemonId}/bio sont désormais restreintes pas le guard.

Pour l'instant le guard n'effectue aucune vérification et laisse donc l'utilisateur accéder à la route. Voyons le cas le plus typique pour le canActivate, comment refuser un utilisateur non-authentifié.

Admettons que vous avez un userService qui implémente une fonction isLoggedIn() qui vous renvoie true si l'utilisateur est connecté, false sinon.

canActivate() {
    console.log("OnlyLoggedInUsers");
    if (this.userService.isLoggedIn()) {
      return true;
    } else {
        window.alert("Vous n'avez pas la permission pour voir cette page");
      return false;
    }
  }
your.guard.ts

Le guard autorisera l'accès à l'utilisateur s'il est connecté. Sinon la navigation sera annulée et vous resterez sur la page courante.

Si vous voulez rediriger votre utilisateur vers une page de connexion ou une page 404, vous pouvez injecter le service Router dans votre guard pour rediriger l'utilisateur avant de renvoyer false.

CanActivateChild

Le guard canActivateChild, fonctionne de la même manière sauf qu'il restreint uniquement l'accès aux routes enfant.

import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, CanActivateChild } from '@angular/router';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class TestChildGuard implements CanActivateChild {
  canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> {
    return true
  }
}
yourChild.guard.ts
const routes: Routes = [
  {path: '', redirectTo: 'home', pathMatch: 'full'},
  {path: 'find', redirectTo: 'search'},
  {path: 'home', component: HomeComponent},
  {path: 'search', component: SearchComponent},
  {
    path: 'pokemon/:pokemonId',
    component: PokemonComponent,
    canActivate: [TestGuard],
    canActivateChild:[TestChildGuard], // <-- Ajout du ChildGuard
    children: [
      {path: '', redirectTo: 'Bio'},
      {path: 'attack', component: AttackComponent},
      {path: 'Bio', component: BiographieComponent},
    ]
  },
  {path: '**', component: HomeComponent}
];
yourChild.guard.ts

La route /pokemon/{pokemonId} n'est pas gérée par le guard, mais les routes enfant /pokemon/{pokemonId}/attack et /pokemon/{pokemonId}/bio sont désormais restreintes pas le guard.

CanDeactivate

Le guard CanDeactivate dans Angular, permet de réagir quand on quitte une route, généralement pour demander une action à l'utilisateur avant qu'il ne quitte la page

Prenons l'exemple d'une demande de confirmation à l'utilisateur pour quitter la page

Dans le composant de la page que vous allez quitter, créez une méthode canExit. Dans la méthode canExit, vous pouvez vérifier s'il y a des données non sauvegardées, etc.

Si oui, demandez la confirmation si l'utilisateur veut quitter la page ou non. Renvoyez true pour quitter le composant sinon retourne false pour rester dans le même composant.

import { Component } from '@angular/core';
 
@Component({
  templateUrl: "register.component.html",
})
export class RegisterComponent    
 
// Vérifiez s'il y a des données non enregistrées, etc. Si oui, alors confirmation
  canExit() : boolean {
 
  if (confirm("Merci de confirmer")) {
      return true
    } else {
      return false
    }
  }
Register.component.ts

Ensuite, créez un service de garde, qui implémente l'interface CanDeactivate.

import { Injectable } from '@angular/core';
import { CanDeactivate } from '@angular/router/src/utils/preactivation';
import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { RegisterComponent } from './register.component';
 
@Injectable()
export class DeactivateGuard implements CanDeactivate<RegisterComponent>
{
    component: Object;
    route: ActivatedRouteSnapshot;
 
   constructor(){
   }
 
   canDeactivate(component:RegisterComponent,
                route: ActivatedRouteSnapshot, 
                state: RouterStateSnapshot,
                nextState: RouterStateSnapshot) : Observable<boolean> | Promise<boolean> | boolean {
        
        return component.canExit();
  }
}
Deactivate.guard.ts

Pour finir mettez à jour votre route :

{ path: 'register', component: RegisterComponent, canDeactivate:[DeactivateGuard] },
app-routing.module.ts

Les paramètres

Vous pouvez voir aussi des paramètres à la fonction du guard implémenter par Angular :

  • route: ActivatedRouteSnapshot
  • state: RouterStateSnapshot

Le paramètre route représente la route où sera dirigé l'utilisateur si le guard renvoi true. Vous pouvez l'utiliser pour extraire les paramètres de cette route pour la logique de votre guard.

Le paramètre state est l'objet qui contient toutes les variables du routeur dans l'état actuel. Idem vous pouvez l'utiliser pour extraires ces variables pour la logique de votre guard.

Conclusion

Voilà l'article touche à sa fin, vous avez vu les concepts de base des guards en espérant que cela vous sera utile.

N'hésitez pas à laisser un commentaire !

Auteur

image auteur
Lucas BENTO-VERSACE Alternant chez Codewise / Étudiant en développement web lucas.bentoversace@ynov.com
"Alternant chez Codewise, passioné par l'informatique et les technologies depuis mon plus jeune âge. J'ai découvert le monde du web et depuis je ne m'en lasse pas ! Fan de jeux vidéos, de sports et de musiques. "