Routage et Navigation : Gérer les Vues de l'Application
Introduction : L'Art de la Navigation dans les Applications Monopages
Dans le monde du développement web moderne, les utilisateurs s'attendent à des applications rapides, fluides et réactives. Les applications web traditionnelles, qui rechargent une page complète à chaque clic sur un lien, offrent une expérience qui peut sembler lente et saccadée. C'est là que les Single Page Applications (SPAs), comme celles développées avec Angular, entrent en jeu.
Une SPA charge une seule fois l'intégralité du code HTML, CSS et JavaScript, puis met à jour dynamiquement le contenu de la page sans nécessiter de rechargements complets. Mais comment une SPA gère-t-elle la navigation entre différentes "vues" ou "pages" sans ces rechargements ? La réponse réside dans le système de routage.
Le Router Angular est le composant clé qui permet à votre application de simuler la navigation entre plusieurs pages en modifiant l'URL du navigateur, tout en affichant les composants Angular appropriés sans recharger l'intégralité de la page. C'est le cerveau qui orchestre le passage d'une vue à l'autre, offrant une expérience utilisateur fluide et intuitive.
Dans cette leçon, nous allons explorer en détail comment configurer et utiliser le Router Angular pour gérer efficacement les vues de votre application :
- Comprendre les principes fondamentaux du routage.
- Mettre en place le module de routage.
- Définir les routes et associer des composants.
- Afficher les vues routées à l'aide de
<router-outlet>. - Naviguer de manière déclarative avec
routerLinket de manière programmatique avec le serviceRouter. - Gérer les paramètres passés via l'URL.
- Aperçu des concepts avancés comme le chargement paresseux et les gardiens de route.
Préparez-vous à donner à vos applications Angular la capacité de naviguer avec élégance !
I. Comprendre le Routage dans Angular
Qu'est-ce que le Routage ?
Le routage, dans le contexte d'une application web, est le processus par lequel l'application mappe une URL spécifique à un composant (ou une vue) particulier et l'affiche à l'utilisateur.
- Applications traditionnelles : Chaque URL correspond à un fichier HTML distinct sur le serveur. Naviguer vers une nouvelle URL entraîne un rechargement complet de la page depuis le serveur.
- Applications monopages (SPAs) : Le routage est géré côté client (par le navigateur et le framework JavaScript). Lorsque l'URL change, le Router Angular intercepte ce changement, détermine quel composant doit être affiché, et le charge dynamiquement dans une zone dédiée de la page, sans rechargement complet.
Avantages du routage côté client :
- Rapidité : Pas de rechargement de page, ce qui rend l'application plus rapide et plus réactive.
- Fluidité : Transition douce entre les vues, améliorant l'expérience utilisateur.
- État de l'application : Permet de maintenir l'état de l'application (par exemple, les données dans un formulaire) lors des changements de vue.
- Partage : Les URL peuvent toujours être partagées, mises en signet et utilisées pour naviguer directement vers un état spécifique de l'application.
Le Router Angular : Le Cœur de la Navigation
Le Router Angular est un module puissant fourni par le framework qui facilite la gestion de la navigation au sein de votre SPA. Il est basé sur le module @angular/router.
Ses principales fonctionnalités incluent :
- Définition de routes : Vous permet d'associer des chemins d'URL à des composants Angular spécifiques.
- Navigation : Offre des mécanismes pour naviguer entre les vues, que ce soit via des liens dans le template ou de manière programmatique depuis le code TypeScript.
- Gestion des paramètres : Permet de passer des données (identifiants, filtres, etc.) d'une vue à l'autre via l'URL.
- Gardiens de route (Guards) : Fournit des "gardes" pour contrôler l'accès aux routes (par exemple, vérifier l'authentification ou l'autorisation avant d'afficher une vue).
- Stratégies de chargement : Prend en charge le chargement paresseux (lazy loading) des modules, ce qui améliore considérablement les performances des grandes applications en ne chargeant le code que lorsque c'il est nécessaire.
II. Configuration et Définition des Routes de Base
Pour commencer à utiliser le routage dans votre application Angular, vous devez configurer le module de routage et définir les routes.
1. Initialisation du Module de Routage
Lorsque vous créez une nouvelle application Angular avec ng new votre-app --routing, Angular génère automatiquement un fichier app-routing.module.ts pour vous. Si vous l'ajoutez plus tard, vous pouvez utiliser ng generate module app-routing --flat --module=app.
Ce module gère la configuration des routes de votre application.
// src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
// Importez les composants que vous souhaitez lier à des routes
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
import { ContactComponent } from './contact/contact.component';
import { NotFoundComponent } from './not-found/not-found.component'; // Pour la gestion 404
// Définition des routes de l'application
const routes: Routes = [
// Route par défaut : redirige l'URL vide vers le composant HomeComponent
{ path: '', component: HomeComponent },
// Route pour la page "À Propos"
{ path: 'about', component: AboutComponent },
// Route pour la page "Contact"
{ path: 'contact', component: ContactComponent },
// Route générique (wildcard) : redirige toutes les autres URL vers NotFoundComponent
// IMPORTANT : Cette route doit être la DERNIÈRE dans votre tableau
{ path: '**', component: NotFoundComponent }
];
@NgModule({
// configure le module de routage principal avec les routes définies
imports: [RouterModule.forRoot(routes)],
// rend le RouterModule disponible pour les autres modules de l'application
exports: [RouterModule]
})
export class AppRoutingModule { }
Explication du code :
import { RouterModule, Routes } from '@angular/router';: Importe les modules nécessaires du package@angular/router.const routes: Routes = [...]: C'est ici que vous définissez vos routes.Routesest une interface d'Angular qui représente un tableau de définitions de routes.{ path: '', component: HomeComponent }: Définit une route.path: Le segment d'URL (chemin) qui active cette route. Ici,''représente la racine de l'application (par exemple,http://localhost:4200/).component: Le composant Angular qui sera affiché lorsque cette route est activée.
{ path: '**', component: NotFoundComponent }: C'est une route générique (wildcard). Le**signifie "n'importe quel chemin non défini précédemment". Il est crucial de la placer en dernier dans votre tableau deRoutes, car le routeur parcourt les routes de haut en bas et s'arrête à la première correspondance. Elle est souvent utilisée pour afficher une page 404 "Page Non Trouvée".
Le RouterModule.forRoot(routes) est utilisé dans le module racine de l'application (AppModule) pour enregistrer les routes principales. Il configure également le service de routage, y compris l'écoute des changements d'URL. Pour les modules de fonctionnalités (abordé plus tard avec le lazy loading), vous utiliseriez RouterModule.forChild(routes).
2. L'Emplacement du Contenu Routé : <router-outlet>
Le Router Angular a besoin d'un endroit pour afficher les composants qui correspondent à la route active. C'est le rôle du composant <router-outlet>.
Vous placez généralement <router-outlet> dans le template du composant racine de votre application, app.component.html, ou dans un autre composant de mise en page où vous souhaitez que le contenu routé s'affiche.
<!-- src/app/app.component.html -->
<header>
<h1>Mon Application Angular</h1>
<nav>
<ul>
<!-- Liens de navigation définis plus bas -->
<li><a routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">Accueil</a></li>
<li><a routerLink="/about" routerLinkActive="active">À Propos</a></li>
<li><a routerLink="/contact" routerLinkActive="active">Contact</a></li>
</ul>
</nav>
</header>
<main class="content">
<!-- C'est ici que les composants routés seront chargés -->
<router-outlet></router-outlet>
</main>
<footer>
<p>© 2023 Mon Application Angular</p>
</footer>
Explication :
- Le
<router-outlet>agit comme un placeholder. Lorsque l'URL correspond à une route, Angular prend le composant associé à cette route et l'injecte à l'intérieur du<router-outlet>. - Lorsque l'URL change et correspond à une autre route, le composant précédemment affiché est détruit et le nouveau composant est chargé.
III. Naviguer entre les Vues
Maintenant que nous avons configuré nos routes et défini où les vues doivent s'afficher, voyons comment les utilisateurs peuvent naviguer entre elles. Angular offre deux méthodes principales pour la navigation : déclarative (via des attributs dans le template) et programmatique (via le code TypeScript).
1. Navigation Déclarative avec routerLink
La méthode la plus courante et la plus simple pour la navigation dans les templates est l'utilisation de la directive routerLink. Elle est généralement appliquée aux éléments <a> (ancres HTML).
<!-- src/app/app.component.html (extrait de la section <nav>) -->
<nav>
<ul>
<li>
<!-- Navigue vers la route par défaut ('/') -->
<a routerLink="/"
routerLinkActive="active"
[routerLinkActiveOptions]="{exact: true}">
Accueil
</a>
</li>
<li>
<!-- Navigue vers la route '/about' -->
<a routerLink="/about"
routerLinkActive="active">
À Propos
</a>
</li>
<li>
<!-- Navigue vers la route '/contact' -->
<a routerLink="/contact"
routerLinkActive="active">
Contact
</a>
</li>
<li>
<!-- Navigue vers une route inexistante pour tester le 404 -->
<a routerLink="/non-existent"
routerLinkActive="active">
Page Inconnue
</a>
</li>
</ul>
</nav>
<!-- Ajoutez ce style simple dans votre src/app/app.component.css -->
<style>
.active {
font-weight: bold;
color: blue;
}
</style>
Explication :
routerLink="/chemin": C'est la forme la plus simple. LerouterLinkest une directive Angular qui remplace le comportement par défaut dehrefpour intercepter la navigation et la gérer via le routeur. Vous pouvez passer une chaîne de caractères ou un tableau d'URL segments (utile pour les routes avec paramètres).- Exemple :
[routerLink]="['/products', productId]"
- Exemple :
routerLinkActive="active": Cette directive applique une ou plusieurs classes CSS (ici,active) à l'élément hôte (<a>) lorsque la route actuelle correspond à celle durouterLink. C'est très utile pour mettre en évidence le lien de la page sur laquelle l'utilisateur se trouve.[routerLinkActiveOptions]="{exact: true}": Utilisé avecrouterLinkActive, cette option est importante pour la route racine (/). Sansexact: true,routerLinkActive="active"sur/serait appliqué à toutes les routes, car toutes les routes "commencent" par/.exact: truegarantit que la classeactiven'est appliquée que si l'URL correspond exactement aurouterLink.
2. Navigation Programmatique avec le Service Router
Parfois, vous avez besoin de déclencher une navigation depuis votre code TypeScript, par exemple après la soumission d'un formulaire, suite à une action de l'utilisateur qui n'est pas un simple clic sur un lien, ou après un traitement asynchrone. Pour cela, vous utilisez le service Router.
D'abord, vous devez injecter le service Router dans le constructeur de votre composant ou service.
// src/app/product-detail/product-detail.component.ts (Exemple de composant de détail de produit)
import { Component } from '@angular/core';
import { Router } from '@angular/router'; // 1. Importez le service Router
@Component({
selector: 'app-product-detail',
template: `
<h2>Détail du Produit</h2>
<p>Ceci est la page de détail d'un produit.</p>
<button (click)="goToProducts()">Retour aux Produits</button>
<button (click)="goToDashboard()">Aller au Tableau de Bord (avec Redirection)</button>
`,
styles: ['button { margin-right: 10px; }']
})
export class ProductDetailComponent {
// 2. Injectez le service Router dans le constructeur
constructor(private router: Router) {}
goToProducts(): void {
// 3. Utilisez la méthode navigate() pour naviguer vers une route
// Le tableau représente les segments de l'URL
this.router.navigate(['/products']); // Navigue vers /products
}
goToDashboard(): void {
// navigate() est préférable pour les chemins absolus ou complexes
this.router.navigate(['/dashboard', 'overview'], { queryParams: { from: 'product-detail' } });
// navigateByUrl() est utile si vous avez déjà l'URL complète sous forme de string
// this.router.navigateByUrl('/dashboard/overview?from=product-detail');
}
}
Explication :
- Injection du service
Router:constructor(private router: Router) {}rend le servicerouterdisponible dans votre composant. this.router.navigate(commands: any[], extras?: NavigationExtras):- Prend un tableau de segments d'URL. C'est flexible et idéal pour construire des chemins complexes, y compris ceux avec des paramètres (ex:
['/products', productId, 'edit']). - Peut prendre un deuxième argument
NavigationExtraspour des options avancées commequeryParams(paramètres de requête),fragment,replaceUrl(remplacer l'entrée dans l'historique du navigateur),relativeTo(pour la navigation relative).
- Prend un tableau de segments d'URL. C'est flexible et idéal pour construire des chemins complexes, y compris ceux avec des paramètres (ex:
this.router.navigateByUrl(url: string | UrlTree, extras?: NavigationExtras):- Prend une URL complète sous forme de chaîne de caractères.
- Utile lorsque vous avez déjà l'URL complète ou si vous récupérez l'URL d'une source externe.
- Moins flexible pour la construction dynamique d'URL avec des paramètres.
Quand utiliser navigate() vs navigateByUrl() ?
- Utilisez
navigate()lorsque vous construisez l'URL à partir de segments et/ou de paramètres dynamiques. C'est la méthode la plus courante. - Utilisez
navigateByUrl()lorsque vous avez déjà l'URL complète sous forme de chaîne de caractères (par exemple, un utilisateur a tapé une URL, ou elle vient d'une API).
IV. Gérer les Paramètres et Données des Routes
Les applications web ont souvent besoin de passer des données entre les vues. Par exemple, une page de liste de produits doit permettre de naviguer vers une page de détail d'un produit spécifique. Le Router Angular offre des mécanismes pour transmettre ces données via l'URL.
Il existe deux types principaux de paramètres : les paramètres de route (path parameters) et les paramètres de requête (query parameters).
1. Paramètres de Route (Path Parameters)
Les paramètres de route sont utilisés pour identifier des ressources spécifiques et font partie intégrante du chemin de l'URL. Par exemple, /products/123 où 123 est l'ID du produit.
1. Définir la route avec un paramètre :
Dans votre app-routing.module.ts (ou le module de routage concerné) :
// src/app/app-routing.module.ts (Extrait)
const routes: Routes = [
// ... autres routes
{ path: 'products/:id', component: ProductDetailComponent }, // ':id' est le paramètre de route
// ... autres routes
];
Le : devant id indique que c'est un paramètre dynamique.
2. Naviguer vers cette route :
- Déclarativement (avec
routerLink) :<!-- Dans un composant ProductListComponent par exemple --> <a [routerLink]="['/products', product.id]">Voir Détails</a> <!-- Si product.id = 123, l'URL sera /products/123 --> - Programmatique (avec
Router.navigate()) :// Dans un composant TypeScript import { Router } from '@angular/router'; constructor(private router: Router) {} showProductDetail(productId: number): void { this.router.navigate(['/products', productId]); }
3. Accéder aux paramètres dans le composant ciblé :
Dans le ProductDetailComponent, vous utiliserez le service ActivatedRoute pour récupérer les paramètres.
// src/app/product-detail/product-detail.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, ParamMap } from '@angular/router'; // Importez ActivatedRoute et ParamMap
import { switchMap } from 'rxjs/operators'; // Utile pour les Observables
@Component({
selector: 'app-product-detail',
template: `
<h2>Détail du Produit : ID {{ productId }}</h2>
<p *ngIf="productName">Nom du Produit : {{ productName }}</p>
<p>Ceci est la page de détail pour le produit avec l'ID {{ productId }}.</p>
<button (click)="goToProducts()">Retour à la liste des produits</button>
`,
})
export class ProductDetailComponent implements OnInit {
productId: string | null = null;
productName: string = '';
// Injectez ActivatedRoute dans le constructeur
constructor(private route: ActivatedRoute /*, private router: Router (si besoin de navigation) */) {}
ngOnInit(): void {
// Méthode 1: Utiliser snapshot (si le composant n'est pas réutilisé pour des IDs différents)
// Utile quand le composant est créé et détruit à chaque navigation
this.productId = this.route.snapshot.paramMap.get('id');
console.log('ID du produit (snapshot):', this.productId);
this.fetchProductDetails(this.productId);
// Méthode 2: S'abonner à paramMap (recommandé pour la réactivité)
// Le paramMap est un Observable. Utilisez-le si les paramètres peuvent changer sans que le composant ne soit détruit (ex: navigation vers /products/1 puis /products/2 sur le même composant)
this.route.paramMap.subscribe((params: ParamMap) => {
this.productId = params.get('id'); // 'id' doit correspondre au nom défini dans la route (:id)
console.log('ID du produit (Observable):', this.productId);
this.fetchProductDetails(this.productId); // Recharger les détails du produit si l'ID change
});
}
private fetchProductDetails(id: string | null): void {
if (id) {
// Simuler la récupération de données
this.productName = `Produit ${id}`;
// Normalement, ici vous feriez un appel à un service (ex: this.productService.getProduct(id))
}
}
// Exemple de fonction pour revenir à la liste des produits
// goToProducts(): void {
// this.router.navigate(['/products']);
// }
}
Explication :
ActivatedRoute: Un service injecté qui contient des informations sur la route activée pour ce composant.snapshot.paramMap.get('id'): Lesnapshotcontient l'état initial des paramètres de la route. C'est suffisant si vous êtes sûr que les paramètres ne changeront pas pendant la durée de vie du composant (c'est-à-dire, si le composant est toujours recréé pour un nouvel ID).paramMap.subscribe(...):paramMapest un Observable. Il est recommandé de s'y abonner si le composant peut être réutilisé lorsque seuls les paramètres de l'URL changent (par exemple, passer de/products/1à/products/2sans quitter leProductDetailComponent). Cela garantit que votre composant réagit aux changements de paramètres.
2. Paramètres de Requête (Query Parameters)
Les paramètres de requête sont des paires clé-valeur ajoutées à la fin de l'URL, après un ?. Ils sont souvent utilisés pour des options de filtrage, de tri, ou pour passer des informations facultatives qui ne font pas partie de l'identification unique de la ressource. Exemple : /products?category=electronics&sort=price.
1. Naviguer avec des paramètres de requête :
Les paramètres de requête ne sont pas définis dans l'objet routes. Ils sont ajoutés lors de la navigation.
// src/app/product-list/product-list.component.ts (Exemple pour une page de liste)
import { Component } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-product-list',
template: `
<h2>Liste des Produits</h2>
<p>Filtres appliqués : Catégorie: {{ category || 'N/A' }}, Tri: {{ sortBy || 'N/A' }}</p>
<button (click)="filterProducts('electronics', 'price-asc')">Filtrer par Electronique & Prix</button>
<button (click)="clearFilters()">Effacer les filtres</button>
`,
styles: ['button { margin-right: 10px; margin-bottom: 10px; }']
})
export class ProductListComponent {
category: string | null = null;
sortBy: string | null = null;
constructor(private router: Router, private route: ActivatedRoute) {}
ngOnInit(): void {
// S'abonner aux changements des paramètres de requête
this.route.queryParamMap.subscribe(params => {
this.category = params.get('category');
this.sortBy = params.get('sort');
console.log('Paramètres de requête mis à jour:', this.category, this.sortBy);
// Ici, vous pouvez déclencher le chargement des produits avec les filtres
});
}
filterProducts(category: string, sortBy: string): void {
// Naviguer vers la route actuelle, mais ajouter/modifier les queryParams
this.router.navigate([], {
relativeTo: this.route, // Garde le chemin de base actuel
queryParams: { category: category, sort: sortBy },
queryParamsHandling: 'merge' // Conserve les queryParams existants et ajoute/modifie ceux-ci
// 'preserve': conserve tous les queryParams existants
// 'merge': fusionne les nouveaux queryParams avec les existants
// null (par défaut) ou '': remplace tous les queryParams
});
}
clearFilters(): void {
// Naviguer vers la route actuelle en supprimant tous les queryParams
this.router.navigate([], {
relativeTo: this.route,
queryParams: null // Supprime tous les paramètres de requête
});
}
}
Explication :
- Navigation : La méthode
navigate()est utilisée avec un tableau vide ([]) comme premier argument pour indiquer que nous restons sur la route actuelle, mais nous ajoutons l'objetqueryParamsdans le deuxième argument (NavigationExtras).relativeTo: this.route: Ceci est crucial. Il indique que la navigation doit être relative à la route actuellement activée. Sans cela,navigate([])vous ramènerait à la racine (/).queryParamsHandling: 'merge': Cette option est très utile.'merge': Ajoute de nouveaux paramètres de requête aux existants ou remplace ceux qui ont le même nom.'preserve': Conserve tous les paramètres de requête existants et ignore ceux que vous essayez d'ajouter.- Par défaut (ou si omis), tous les paramètres de requête existants seraient supprimés à moins d'être explicitement inclus.
- Accès aux paramètres : Comme pour les paramètres de route, vous utilisez
ActivatedRoutemais cette fois, vous vous abonnez àqueryParamMappour obtenir les valeurs des paramètres de requête.
V. Concepts Avancés (Aperçu)
Le Router Angular est incroyablement riche en fonctionnalités. Voici un bref aperçu de quelques concepts avancés qui améliorent la performance et la sécurité de vos applications.
1. Chargement Paresseux (Lazy Loading)
Pour les grandes applications, charger tout le code de l'application dès le démarrage peut entraîner des temps de chargement initiaux importants. Le chargement paresseux (Lazy Loading) permet de charger des modules de fonctionnalités uniquement lorsque l'utilisateur y accède.
- Avantage : Réduit la taille du bundle initial de l'application, ce qui améliore les performances et l'expérience utilisateur, en particulier sur les connexions lentes.
- Fonctionnement : Au lieu de spécifier directement un
componentdans la définition de la route, vous utilisezloadChildrenpour pointer vers le module que vous souhaitez charger de manière paresseuse.
// src/app/app-routing.module.ts (Extrait)
const routes: Routes = [
// ...
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)
},
// ...
];
Lorsque l'utilisateur navigue vers /admin, le module AdminModule et tous ses composants et services sont chargés en arrière-plan.
2. Gardiens de Route (Route Guards)
Les gardiens de route sont des classes que vous implémentez pour contrôler l'accès aux routes, la capacité de naviguer vers ou de quitter une route, ou même le chargement d'un module paresseux.
- Utilité : Gérer l'authentification, l'autorisation, la confirmation de sauvegarde de formulaires non enregistrés, etc.
- Types courants :
CanActivate: Décide si une route peut être activée (si l'utilisateur peut y naviguer).CanDeactivate: Décide si une route peut être désactivée (si l'utilisateur peut la quitter).CanLoad: Décide si un module paresseux peut être chargé du tout.
Exemple d'application d'un gardien CanActivate :
// src/app/app-routing.module.ts (Extrait)
import { AuthGuard } from './auth.guard'; // Supposons que vous avez un AuthGuard
const routes: Routes = [
// ...
{ path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard] },
// ...
];
Ici, DashboardComponent ne sera activé que si AuthGuard retourne true.
Conclusion : Maîtriser le Flux de l'Application
Le Router Angular est bien plus qu'un simple gestionnaire d'URL ; c'est un pilier central du développement d'applications Single Page Applications (SPAs) modernes et robustes. Il vous donne le pouvoir de créer des expériences utilisateur dynamiques et fluides, en donnant l'illusion de pages distinctes sans les inconvénients des rechargements complets.
Au cours de cette leçon, vous avez acquis les compétences fondamentales pour :
- Comprendre l'importance du routage dans le contexte des SPAs Angular.
- Initialiser et configurer votre module de routage principal.
- Définir des routes claires et logiques, en associant des chemins URL à des composants Angular spécifiques.
- Utiliser
<router-outlet>comme point d'ancrage dynamique pour vos vues routées. - Naviguer efficacement dans votre application, que ce soit de manière déclarative avec
routerLinkou de manière programmatique via le serviceRouter. - Transmettre et récupérer des données entre les vues en utilisant les paramètres de route (
path parameters) et les paramètres de requête (query parameters).
La maîtrise de ces concepts est essentielle pour créer des applications Angular complexes et performantes. En explorant davantage les concepts avancés comme le chargement paresseux et les gardiens de route, vous pourrez optimiser la performance et la sécurité de vos applications, offrant ainsi une expérience utilisateur encore plus enrichissante. Le Router Angular est votre allié pour sculpter le flux de navigation de votre application avec précision et agilité.