NgRx et state management Angular : état, formulaires et sécurité
Objectif : structurer l’état global proprement, concevoir des formulaires avancés et sécuriser l’application avec JWT, Guards et bonnes pratiques modernes…
Objectifs pédagogiques du Jour 3
Ce module consolide la partie “mécanique” d’une application professionnelle : comment l’état est stocké, comment l’utilisateur interagit via les formulaires, et comment l’accès est sécurisé.
Résultats attendus
À la fin du jour 3, vous serez capable de :
- Choisir une stratégie de gestion d’état adaptée à votre contexte (Signals, RxJS, NgRx).
- Concevoir des formulaires complexes, robustes et orientés UX.
- Mettre en place une authentification JWT sécurisée côté Angular + Spring Boot.
Gains business & techniques
Impact sur vos projets
- Réduction de la dette technique liée au “state bricolé partout”.
- Moins de bugs fonctionnels sur les formulaires (validation cohérente, claire).
- Sécurité renforcée sans complexifier inutilement le code.
1. Gestion d’État Réactive (RxJS, NgRx, Signals)
L’état mal géré est une source majeure de bugs et de régressions. L’objectif : arrêter de diffuser des BehaviorSubject partout et structurer un vrai state management.
Scénario simple
State local avec Signals (Angular moderne)
Idéal pour une feature isolée (catalogue, panier, écran spécifique) : lisible, local, facile à tester.
// Store local de panier basé sur Signals
@Injectable({ providedIn: 'root' })
export class CartStore {
readonly items = signal<Product[]>([]);
readonly total = computed(
() => this.items().reduce((acc, p) => acc + p.price, 0)
);
add(product: Product) {
this.items.update(items => [...items, product]);
}
clear() {
this.items.set([]);
}
}
Gains
- Aucun
subscribe()à gérer → moins de fuites mémoire. - Debug très simple : un seul point d’entrée pour l’état.
- Plus lisible qu’un mélange Observables/Subjects improvisés.
Cas applicatif large
NgRx / Store global pour les gros projets
Pour les applications avec beaucoup d’écrans, de rôles et de règles métier, un Store global reste pertinent.
// Exemple d'action NgRx
export const addItemToCart = createAction(
'[Cart] Add Item',
props<{ product: Product }>()
);
// Exemple de reducer
const reducer = createReducer(
initialState,
on(addItemToCart, (state, { product }) => ({
...state,
items: [...state.items, product]
}))
);
Bon réflexe
exposez le store via un service “Facade” (ex : CartFacade) pour ne pas coupler vos composants directement à NgRx.
À éviter absolument
- Un mélange ad hoc : un peu de
Subject, un peu destore, un peu delocalStorage… - Du state global pour tout (inclus des infos qui pourraient rester locales au composant).
- Des selectors qui contiennent de la logique métier complexe (la sortir dans des services).
2. Formulaires Complexes & UX Pro
L’utilisateur peut pardonner une erreur d’affichage, beaucoup moins un formulaire qui se comporte mal. Ici, on structure des Reactive Forms robustes.
Structure
Formulaire de commande (formulaire imbriqué)
this.orderForm = this.fb.group({
customer: ['', Validators.required],
email: ['', [Validators.required, Validators.email]],
confirmEmail: ['', Validators.required],
address: this.fb.group({
street: ['', Validators.required],
city: ['', Validators.required],
zip: ['', [Validators.required, Validators.pattern(/^\d{5}$/)]]
}),
acceptTerms: [false, Validators.requiredTrue]
}, { validators: emailsMatch }); // validator croisé posé sur le groupe
Astuce
regroupez les champs liés dans des FormGroup (adresse, infos de paiement, etc.), cela simplifie la validation et la lecture.
Validation avancée
Validation croisée & logique dynamique
import { AbstractControl, ValidationErrors } from '@angular/forms';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
// Validator croisé (ex: confirmation d'email) — à poser sur le FormGroup
export function emailsMatch(group: AbstractControl): ValidationErrors | null {
const email = group.get('email')?.value;
const confirm = group.get('confirmEmail')?.value;
return email && confirm && email !== confirm ? { emailsMismatch: true } : null;
}
// Écoute des changements (désabonnement automatique à la destruction du composant)
this.orderForm.valueChanges
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(value => {
// adapter dynamiquement les options de livraison, par ex.
});
Bonnes pratiques
- Externaliser les validators dans des fichiers dédiés.
- Rendre les messages d’erreur explicites et humains.
- Préférer Reactive Forms dès qu’il y a du métier ou des règles évolutives.
Astuce d’expert
Depuis Angular 14, les Reactive Forms sont typés. Injectez NonNullableFormBuilder (ou fb.nonNullable.group(...)) : form.getRawValue() renvoie alors un objet exactement typé ({ email: string; ... }), sans null parasite, que vous pouvez passer directement à votre service HTTP.
3. Sécurité & Authentification côté Angular
L’authentification ne se limite pas à “vérifier si un token existe”. Il s’agit de protéger les routes, filtrer les appels et gérer les rôles correctement.
HTTP
Injection du JWT via un HttpInterceptor
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
// Intercepteur fonctionnel (Angular 15+, recommandé avec provideHttpClient)
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const token = inject(AuthService).getToken();
if (!token) return next(req);
const cloned = req.clone({
setHeaders: { Authorization: `Bearer ${token}` }
});
return next(cloned);
};
// app.config.ts
// provideHttpClient(withInterceptors([authInterceptor]))
Astuce
centraliser ici tous les headers communs (langue, correlation-id, etc.) au lieu de répéter dans chaque service.
Routes
Protection des routes avec un Guard
import { CanActivateFn, Router } from '@angular/router';
import { inject } from '@angular/core';
// Guard fonctionnel (l'interface CanActivate est dépréciée depuis Angular 15.2)
export const authGuard: CanActivateFn = () => {
const auth = inject(AuthService);
return auth.isLoggedIn() ? true : inject(Router).createUrlTree(['/login']);
};
// app.routes.ts
// { path: 'orders', canActivate: [authGuard], loadComponent: ... }
Pattern
Role-Based Access Control (RBAC) + Guards → règles de sécurité centralisées, simples à tester.
4. Authentification JWT côté Spring Boot
Spring Boot s’occupe de l’authentification, de la génération et de la validation du token. Angular consomme simplement l’API exposée.
Login
Endpoint d’authentification
@RestController
@RequestMapping("/api/auth")
public class AuthController {
private final AuthService service;
public AuthController(AuthService service) { // sans constructeur, le champ final ne compile pas
this.service = service;
}
@PostMapping("/login")
public ResponseEntity<TokenResponse> login(@Valid @RequestBody LoginRequest req) {
return ResponseEntity.ok(service.authenticate(req));
}
}
Bonnes pratiques
Points de vigilance côté backend
- Limiter la durée de vie des tokens (access token court, refresh token dédié).
- Ne jamais mettre d’information sensible en clair dans le JWT.
- Protéger les endpoints d’admin par rôle et non uniquement par présence du token.
Important
si possible, préférer les cookies HttpOnly pour le stockage des tokens plutôt que localStorage (protection XSS).
Suivez votre progression. Créez un compte gratuit pour cocher vos leçons et reprendre là où vous en étiez.
Créer un compte gratuit