Guide de Migration: Angular v16 vers v21
Guide complet pour migrer les applications Angular de la v16 vers la v21.
Vue d’ensemble
Section intitulée « Vue d’ensemble »Angular v21 introduit plusieurs changements majeurs:
- Composants standalone par défaut
- Nouvelle syntaxe de flux de contrôle (@if, @for, @switch)
- APIs basées sur les signaux pour la réactivité
- Guards fonctionnels remplaçant les guards basés sur des classes
- Nouveau bootstrapping avec
bootstrapApplication()
Étapes de Migration
Section intitulée « Étapes de Migration »1. Mettre à Jour les Dépendances
Section intitulée « 1. Mettre à Jour les Dépendances »# Mettre à jour Angular CLI et Coreng update @angular/core@21 @angular/cli@21
# Mettre à jour Angular Material (si utilisé)ng update @angular/material@212. Migrer vers les Composants Standalone
Section intitulée « 2. Migrer vers les Composants Standalone »Angular v21 utilise les composants standalone par défaut. Exécutez la migration automatique:
ng generate @angular/core:standaloneCette migration:
- Convertit les composants en standalone
- Met à jour les tableaux d’imports
- Supprime les déclarations NgModule
- Met à jour la configuration de routage
3. Mettre à Jour la Configuration de Bootstrap
Section intitulée « 3. Mettre à Jour la Configuration de Bootstrap »Remplacer le bootstrap NgModule par bootstrapApplication().
Avant (v16):
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';import { AppModule } from './app/app.module';
platformBrowserDynamic() .bootstrapModule(AppModule) .catch(err => console.error(err));Après (v21):
import { bootstrapApplication } from '@angular/platform-browser';import { provideRouter } from '@angular/router';import { provideHttpClient } from '@angular/common/http';import { provideAnimations } from '@angular/platform-browser/animations';import { AppComponent } from './app/app.component';import { routes } from './app/app.routes';
bootstrapApplication(AppComponent, { providers: [ provideRouter(routes), provideHttpClient(), provideAnimations() ]});4. Convertir les Guards en Style Fonctionnel
Section intitulée « 4. Convertir les Guards en Style Fonctionnel »Les guards basés sur des classes sont dépréciés dans la v21. Convertir en guards fonctionnels.
Avant (v16):
import { Injectable } from '@angular/core';import { CanActivate, Router } from '@angular/router';
@Injectable({ providedIn: 'root' })export class AuthGuard implements CanActivate { constructor(private authService: AuthService, private router: Router) {}
canActivate(): boolean { if (this.authService.isLoggedIn()) { return true; } this.router.navigate(['/login']); return false; }}Après (v21):
import { inject } from '@angular/core';import { CanActivateFn, Router } from '@angular/router';
export const authGuard: CanActivateFn = (route, state) => { const authService = inject(AuthService); const router = inject(Router);
if (authService.isLoggedIn()) { return true; } router.navigate(['/login']); return false;};Appliquer aux routes:
const routes: Routes = [ { path: 'admin', component: AdminComponent, canActivate: [authGuard] // Utiliser le guard fonctionnel }];5. Migrer la Syntaxe de Flux de Contrôle
Section intitulée « 5. Migrer la Syntaxe de Flux de Contrôle »Angular v21 introduit une nouvelle syntaxe de template remplaçant les directives structurelles.
*ngIf → @if
Section intitulée « *ngIf → @if »Avant (v16):
<div *ngIf="user"> Welcome {{ user.name }}</div><div *ngIf="!user"> Please log in</div>Après (v21):
@if (user) { <div>Welcome {{ user.name }}</div>} @else { <div>Please log in</div>}*ngFor → @for
Section intitulée « *ngFor → @for »Avant (v16):
<div *ngFor="let album of albums; let i = index; trackBy: trackByFn"> {{ i + 1 }}. {{ album.name }}</div>Après (v21):
@for (album of albums; track album.id) { <div>{{ $index + 1 }}. {{ album.name }}</div>}Variables intégrées:
$index- index actuel$first- true si premier élément$last- true si dernier élément$even- true si index pair$odd- true si index impair$count- nombre total d’éléments
[ngSwitch] → @switch
Section intitulée « [ngSwitch] → @switch »Avant (v16):
<div [ngSwitch]="status"> <p *ngSwitchCase="'loading'">Loading...</p> <p *ngSwitchCase="'error'">Error occurred</p> <p *ngSwitchCase="'success'">Success!</p> <p *ngSwitchDefault>Unknown status</p></div>Après (v21):
@switch (status) { @case ('loading') { <p>Loading...</p> } @case ('error') { <p>Error occurred</p> } @case ('success') { <p>Success!</p> } @default { <p>Unknown status</p> }}6. Adopter les APIs Basées sur les Signaux
Section intitulée « 6. Adopter les APIs Basées sur les Signaux »Angular v21 met l’accent sur les signaux pour la gestion d’état réactive.
Convertir les Observables en Signaux
Section intitulée « Convertir les Observables en Signaux »Avant (v16):
export class AlbumListComponent { albums$ = this.albumService.getAlbums();
constructor(private albumService: AlbumService) {}}<div *ngFor="let album of albums$ | async"> {{ album.name }}</div>Après (v21):
import { toSignal } from '@angular/core/rxjs-interop';
export class AlbumListComponent { albums = toSignal(this.albumService.getAlbums(), { initialValue: [] });
constructor(private albumService: AlbumService) {}}@for (album of albums(); track album.id) { <div>{{ album.name }}</div>}Utiliser les Signaux Calculés
Section intitulée « Utiliser les Signaux Calculés »Avant (v16):
export class CartComponent { cart$ = this.cartService.cart$;
total$ = this.cart$.pipe( map(items => items.reduce((sum, item) => sum + item.price, 0)) );}Après (v21):
import { computed } from '@angular/core';
export class CartComponent { cart = this.cartService.cart; // signal
total = computed(() => this.cart().reduce((sum, item) => sum + item.price, 0) );}Remplacer BehaviorSubject par Signal
Section intitulée « Remplacer BehaviorSubject par Signal »Avant (v16):
export class CartService { private cartSubject = new BehaviorSubject<Album[]>([]); cart$ = this.cartSubject.asObservable();
addToCart(album: Album) { const current = this.cartSubject.value; this.cartSubject.next([...current, album]); }}Après (v21):
import { signal } from '@angular/core';
export class CartService { cart = signal<Album[]>([]);
addToCart(album: Album) { this.cart.update(items => [...items, album]); }}7. Utiliser httpResource pour le Chargement de Données
Section intitulée « 7. Utiliser httpResource pour le Chargement de Données »Angular v21 introduit l’API Resource pour les requêtes HTTP déclaratives.
Avant (v16):
export class AlbumListComponent implements OnInit { albums: Album[] = []; loading = false; error: string | null = null;
ngOnInit() { this.loading = true; this.albumService.getAlbums().subscribe({ next: (albums) => { this.albums = albums; this.loading = false; }, error: (err) => { this.error = err.message; this.loading = false; } }); }}Après (v21):
import { httpResource } from '@angular/core';
export class AlbumListComponent { albumsResource = httpResource({ request: () => ({ url: '/api/albums' }), loader: () => this.http.get<Album[]>('/api/albums') });
albums = computed(() => this.albumsResource.value() ?? []); loading = computed(() => this.albumsResource.isLoading()); error = computed(() => this.albumsResource.error());}8. Migrer vers les Formulaires à Signaux
Section intitulée « 8. Migrer vers les Formulaires à Signaux »Angular v21 introduit l’API de formulaires basée sur les signaux.
Avant (v16):
import { FormBuilder, Validators } from '@angular/forms';
export class AlbumFormComponent { albumForm = this.fb.group({ name: ['', Validators.required], artist: ['', Validators.required], price: [0, [Validators.required, Validators.min(0)]] });
constructor(private fb: FormBuilder) {}}<form [formGroup]="albumForm"> <input formControlName="name"> <input formControlName="artist"> <input formControlName="price" type="number"></form>Après (v21):
import { form, Field, required, min } from '@angular/forms/signals';
export class AlbumFormComponent { albumForm = form( signal<Album>({ id: 0, name: '', artist: '', price: 0 }), (path) => { required(path.name); required(path.artist); required(path.price); min(path.price, 0); } );}<form> <input [field]="albumForm.path.name"> <input [field]="albumForm.path.artist"> <input [field]="albumForm.path.price" type="number"></form>9. Adopter @ngrx/signals pour la Gestion d’État
Section intitulée « 9. Adopter @ngrx/signals pour la Gestion d’État »Pour un état complexe, utiliser NgRx SignalStore.
Avant (v16):
import { BehaviorSubject } from 'rxjs';
export class AlbumStore { private albumsSubject = new BehaviorSubject<Album[]>([]); albums$ = this.albumsSubject.asObservable();
addAlbum(album: Album) { this.albumsSubject.next([...this.albumsSubject.value, album]); }}Après (v21):
import { signalStore, withState, withMethods, withComputed } from '@ngrx/signals';
export const AlbumStore = signalStore( { providedIn: 'root' }, withState({ albums: [] as Album[] }), withComputed(({ albums }) => ({ albumCount: computed(() => albums().length) })), withMethods((store) => ({ addAlbum(album: Album) { patchState(store, { albums: [...store.albums(), album] }); } })));Liste de Contrôle de Migration
Section intitulée « Liste de Contrôle de Migration »- Mettre à jour Angular CLI et Core vers v21
- Exécuter le schéma de migration standalone
- Mettre à jour
main.tspour utiliserbootstrapApplication() - Supprimer
app.module.tset les fichiers NgModule - Convertir les guards basés sur des classes en guards fonctionnels
- Remplacer
*ngIf,*ngFor,[ngSwitch]par@if,@for,@switch - Convertir les observables en signaux avec
toSignal() - Remplacer les BehaviorSubjects par
signal() - Utiliser
computed()pour l’état dérivé - Adopter
httpResourcepour les requêtes HTTP - Migrer vers les formulaires basés sur les signaux
- Considérer @ngrx/signals pour un état complexe
- Mettre à jour les tests pour la nouvelle syntaxe
- Tester l’application de manière approfondie
Problèmes Courants
Section intitulée « Problèmes Courants »Problème: Dépendance Circulaire
Section intitulée « Problème: Dépendance Circulaire »Problème: Les composants standalone peuvent créer des dépendances circulaires.
Solution: Extraire les composants partagés dans un fichier séparé ou utiliser des imports dynamiques.
Problème: Configuration des Providers
Section intitulée « Problème: Configuration des Providers »Problème: Services introuvables après suppression des NgModules.
Solution: S’assurer que les services utilisent providedIn: 'root' ou les ajouter au tableau providers dans bootstrapApplication().
Problème: Chargement Différé
Section intitulée « Problème: Chargement Différé »Problème: Les routes de chargement différé ne fonctionnent pas après la migration.
Solution: Mettre à jour la configuration des routes:
// Avant{ path: 'admin', loadChildren: () => import('./admin/admin.module') }
// Après{ path: 'admin', loadComponent: () => import('./admin/admin.component') }Problème: FormsModule/ReactiveFormsModule
Section intitulée « Problème: FormsModule/ReactiveFormsModule »Problème: Les formulaires ne fonctionnent pas après suppression des NgModules.
Solution: Importer dans le composant:
import { ReactiveFormsModule } from '@angular/forms';
@Component({ imports: [ReactiveFormsModule], // ...})Bonnes Pratiques
Section intitulée « Bonnes Pratiques »- Migration Graduelle: Migrer une fonctionnalité à la fois
- Couverture des Tests: S’assurer d’une bonne couverture de tests avant la migration
- Utiliser les Schematics: Laisser Angular CLI gérer les migrations répétitives
- Adopter les Signaux: Embrasser les APIs basées sur les signaux pour de meilleures performances
- Simplifier l’État: Utiliser les signaux pour réduire la complexité RxJS
- Sûreté des Types: Tirer parti de TypeScript avec les signaux
- Suivre le Guide Officiel: Consulter le Guide de Mise à Jour Angular

