Appearance
Bonnes Pratiques UX pour le routing dans Angular
Le routing est un élément crucial de toute application web moderne. Une bonne gestion du routing permet d'offrir une expérience utilisateur fluide et intuitive. Imaginons un client dans un grand magasin : il ne voudrait pas avoir à retourner à l'entrée chaque fois qu'il change de rayon. C'est la même chose pour votre application !
Conservation du contexte de navigation
Le ReturnUrl
pattern
CONSEIL
Toujours sauvegarder la route actuelle avant une redirection forcée (comme vers la page de login) permet à l'utilisateur de reprendre exactement là où il en était.
Voici comment implémenter cette fonctionnalité :
ts
import { inject } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from './auth.service';
export const authGuard = () => {
const router = inject(Router);
const authService = inject(AuthService);
if (authService.isAuthenticated()) {
return true;
}
return router.createUrlTree(['/login'], {
queryParams: { returnUrl: router.routerState.snapshot.url }
});
};
ts
import { Component, inject } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
// AuthService serait un service d'authentification qui permettrait de se connecter à l'application
import { AuthService } from './auth.service';
@Component({
standalone: true,
selector: 'app-login',
imports: [FormsModule],
template: `
@if (error) {
<div class="error">{{ error }}</div>
}
<form (ngSubmit)="onSubmit()">
<!-- Formulaire de login -->
</form>
`
})
export class LoginComponent {
private route = inject(ActivatedRoute);
private router = inject(Router);
private authService = inject(AuthService);
async onSubmit() {
try {
// Pour faciliter, ça renvoie une promesse mais ça pourrait être un observable dans votre code
await this.authService.login();
const returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
await this.router.navigateByUrl(returnUrl);
} catch (error) {
this.error = 'Échec de la connexion';
}
}
}
ts
import { Routes } from '@angular/router';
import { authGuard } from './auth.guard';
export const routes: Routes = [
{
path: 'users',
loadComponent: () => import('./users/users.component'), // on utilise le lazy loading pour charger le composant
canActivate: [authGuard] // Protection de la route avec notre guard
},
{
path: 'login',
loadComponent: () => import('./login/login.component') // on utilise le lazy loading aussi
}
];
La méthode createUrlTree
La méthode createUrlTree
est une méthode moderne d'Angular qui remplace le retour booléen traditionnel des guards. Elle permet de :
- Créer directement un arbre d'URL pour la redirection
- Éviter les problèmes de timing liés aux redirections asynchrones (quand plusieurs redirections se produisent en même temps, certaines peuvent être perdues ou exécutées dans le mauvais ordre)
- Ajouter facilement des paramètres de requête
COMPRENDRE L'ARBRE D'URL
Un arbre d'URL (UrlTree) est une structure de données qui représente une URL décomposée en segments. Imaginez un arbre où chaque segment de l'URL est une branche :
/users/123/edit?returnUrl=/dashboard
│
├── users
│ └── 123
│ └── edit
└── queryParams
└── returnUrl: '/dashboard'
Par exemple, pour construire cet arbre :
ts
// Construction simple d'un UrlTree
router.createUrlTree(['/users', '123', 'edit']);
// Avec des paramètres de requête
router.createUrlTree(
['/users', '123', 'edit'],
{
queryParams: { returnUrl: '/dashboard' }
}
);
Cette structure permet à Angular de gérer efficacement la navigation et les paramètres associés.
La propriété router.routerState.snapshot.url
capture l'URL complète actuelle avant la redirection. Par exemple, si l'utilisateur essaie d'accéder à /users/123/edit
, cette URL sera sauvegardée intégralement.
Récupération du returnUrl
Dans le composant de login, la ligne :
ts
const returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
fait deux choses :
- Récupère le paramètre
returnUrl
de l'URL (ex:/login?returnUrl=/users/123/edit
) - Utilise '/' comme fallback si le paramètre n'existe pas, assurant ainsi une redirection par défaut vers la page d'accueil
ASTUCE
Vous pouvez également utiliser cette technique pour d'autres cas de redirection, comme après une action qui nécessite une confirmation.
Gestion du Rafraîchissement
ATTENTION
Le comportement par défaut du router lors d'un rafraîchissement peut parfois être déroutant pour l'utilisateur.
Parfois, le comportement par défaut du router lors d'un rafraîchissement peut empêcher de recharger le composant comme attendu. Pour ces cas, vous pouvez configurer l'option onSameUrlNavigation
ts
this.router.navigate(['/users'], {
relativeTo: this.route,
onSameUrlNavigation: 'reload' // Force le rechargement même sur la même URL
});
La propriété onSameUrlNavigation
configure comment Angular doit réagir quand l'utilisateur navigue vers l'URL actuelle. Par exemple, si l'utilisateur est sur /users
et clique sur un lien qui mène aussi vers /users
:
- Avec
'ignore'
(défaut) : Angular ne fait rien - Avec
'reload'
: Angular recharge le composant, même si l'URL est identique
EXEMPLE CONCRET
Imaginez une liste d'utilisateurs avec un bouton "Rafraîchir" :
- Sans
reload
: cliquer sur le bouton ne rechargerait pas la liste - Avec
reload
: la liste est mise à jour, même si l'URL ne change pas
Cette configuration est particulièrement utile pour :
- Les pages avec des données en temps réel
- Les listes qui doivent être rafraîchies
- Les formulaires qui doivent être réinitialisés
Feedback Visuel pendant la Navigation
Pour améliorer l'expérience utilisateur, ajoutez des indicateurs de chargement :
ts
import { Injectable } from '@angular/core';
import { signal } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class LoadingService {
loading = signal(false);
setLoading(value: boolean) {
this.loading.set(value);
}
}
ts
import { Component, inject } from '@angular/core';
import { Router, Event, NavigationStart, NavigationEnd } from '@angular/router';
import { LoadingService } from './loading.service';
@Component({
standalone: true,
selector: 'app-root',
template: `
@if (loadingService.loading()) {
<div class="loading-indicator">Chargement...</div>
}
<router-outlet />
`
})
export class AppComponent {
private router = inject(Router);
private loadingService = inject(LoadingService);
constructor() {
this.router.events.subscribe((event: Event) => {
if (event instanceof NavigationStart) {
this.loadingService.setLoading(true);
}
if (event instanceof NavigationEnd) {
this.loadingService.setLoading(false);
}
});
}
}
Le router Angular émet différents événements pendant la navigation. Les plus importants sont :
NavigationStart
: émis quand la navigation commenceNavigationEnd
: émis quand la navigation se termine avec succès
Dans notre exemple :
ts
this.router.events.subscribe((event: Event) => {
if (event instanceof NavigationStart) {
this.loadingService.setLoading(true); // Affiche l'indicateur de chargement
}
if (event instanceof NavigationEnd) {
this.loadingService.setLoading(false); // Cache l'indicateur de chargement
}
});
Cette approche permet de :
- Détecter le début de la navigation pour montrer un indicateur de chargement
- Détecter la fin de la navigation pour cacher l'indicateur
- Informer visuellement l'utilisateur que l'application traite sa demande
OPTIMISATION
Utilisez les signaux Angular pour une gestion réactive et performante de l'état de chargement.
En savoir plus sur les événements du router