State Management, Formulaires & 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 Angular + Spring Boot.
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é.
À 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.
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.
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([]);
}
}
- 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.
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]
}))
);
CartFacade) pour ne pas coupler vos composants directement à NgRx.
- 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.
Formulaire de commande (formulaire imbriqué)
this.orderForm = this.fb.group({
customer: ['', Validators.required],
email: ['', [Validators.required, Validators.email]],
address: this.fb.group({
street: ['', Validators.required],
city: ['', Validators.required],
zip: ['', Validators.required]
}),
acceptTerms: [false, Validators.requiredTrue]
});
FormGroup
(adresse, infos de paiement, etc.), cela simplifie la validation et la lecture.
Validation croisée & logique dynamique
// Validator croisé (ex: confirmation d'email)
export function emailsMatch(group: AbstractControl) {
const email = group.get('email')?.value;
const confirm = group.get('confirmEmail')?.value;
return email && confirm && email !== confirm ? { emailsMismatch: true } : null;
}
// Écoute des changements
this.orderForm.valueChanges.subscribe(value => {
// adapter dynamiquement les options de livraison, par ex.
});
- 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.
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.
Injection du JWT via un HttpInterceptor
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private auth: AuthService) {}
intercept(req: HttpRequest<any>, next: HttpHandler) {
const token = this.auth.getToken();
if (!token) return next.handle(req);
const cloned = req.clone({
setHeaders: { Authorization: `Bearer ${token}` }
});
return next.handle(cloned);
}
}
Protection des routes avec un Guard
@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
constructor(private auth: AuthService, private router: Router) {}
canActivate(): boolean {
if (!this.auth.isLoggedIn()) {
this.router.navigate(['/login']);
return false;
}
return true;
}
}
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.
Endpoint d’authentification
@RestController
@RequestMapping("/api/auth")
public class AuthController {
private final AuthService service;
@PostMapping("/login")
public ResponseEntity<TokenResponse> login(@RequestBody LoginRequest req) {
return ResponseEntity.ok(service.authenticate(req));
}
}
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.
localStorage (protection XSS).
