Angular 21 : httpResource(), template diagnostics, HMR natif, zoneless stable, performances CLI esbuild et guide de migration v20 → v21.
Angular 21 : vue d'ensemble et contexte
Angular 21 s'inscrit dans la continuité de la refonte majeure entamée avec la v17 : suppression progressive de Zone.js, montée en puissance des Signals, et rationalisation de la CLI. Cette version consolide des fonctionnalités qui étaient en phase expérimentale depuis la v19 et v20, et en stabilise plusieurs pour la production.
Depuis Angular 17, l'équipe suit un rythme de publication de deux versions majeures par an (en mai et novembre). Angular 21 est sorti en novembre 2024 et apporte des changements significatifs dans quatre domaines :
- HTTP et données : la nouvelle API
httpResource()basée sur les Signals remplace les patterns Observable classiques pour les cas d'usage simples - Developer Experience : les diagnostics de templates sont beaucoup plus précis et actionnables
- Performance runtime : le mode zoneless devient stable et le HMR est intégré nativement dans la CLI
- Performance build : esbuild continue de repousser les limites de vitesse de compilation
ng update. La plupart des nouveautés sont opt-in — vous les activez quand vous êtes prêt.
Voici un aperçu comparatif des grandes évolutions entre v20 et v21 :
| Fonctionnalité | Angular 20 | Angular 21 |
|---|---|---|
httpResource() |
Expérimental (Developer Preview) | Stable |
| Zoneless Change Detection | Developer Preview | Stable |
| HMR (Hot Module Replacement) | Opt-in via flag | Activé par défaut |
| Template diagnostics | Erreurs génériques | Messages contextuels + suggestions |
| esbuild (application builder) | Défaut pour nouveaux projets | Migrations automatiques webpack → esbuild |
| Signal inputs / outputs | Stable | Améliorations de type narrowing |
Commençons par la fonctionnalité la plus attendue : httpResource().
httpResource() : la révolution HTTP signal-based
httpResource() est la nouvelle façon de faire des requêtes HTTP dans Angular 21. Elle repose entièrement sur les Signals et le système de réactivité introduit en v16, et offre une alternative plus simple au duo HttpClient + Observable pour les cas d'usage CRUD courants.
Pourquoi httpResource() change tout
Avec l'approche classique, récupérer des données et gérer les états (loading, erreur, données) nécessitait entre 15 et 30 lignes de code boilerplate. Avec httpResource(), c'est une ligne — et tout le reste est automatique.
httpResource() retourne un objet avec des Signals pour value(), status(), error() et isLoading(). Pas d'Observable, pas de subscribe(), pas de async pipe.
Exemple minimal : récupérer une liste
// src/app/components/user-list/user-list.component.ts
import { Component, inject } from '@angular/core';
import { httpResource } from '@angular/common/http'; // Import depuis @angular/common/http
// Interface TypeScript pour typer la réponse
interface User {
id: number;
name: string;
email: string;
}
@Component({
selector: 'app-user-list',
standalone: true,
template: `
<!-- Affichage conditionnel basé sur les Signals -->
@if (users.isLoading()) {
<p>Chargement en cours...</p>
} @else if (users.error()) {
<p class="text-danger">Erreur : {{ users.error()?.message }}</p>
} @else {
<ul>
@for (user of users.value(); track user.id) {
<li>{{ user.name }} — {{ user.email }}</li>
}
</ul>
}
`
})
export class UserListComponent {
// httpResource() : une seule ligne pour tout gérer
// La requête est déclenchée automatiquement au montage du composant
users = httpResource<User[]>('/api/users');
}
Requête dynamique réactive avec un Signal paramètre
La vraie puissance de httpResource() est sa réactivité : si un Signal paramètre change, la requête se relance automatiquement — comme un computed() qui fait du HTTP.
// src/app/components/user-detail/user-detail.component.ts
import { Component, input, inject } from '@angular/core';
import { httpResource } from '@angular/common/http';
interface UserDetail {
id: number;
name: string;
role: string;
createdAt: string;
}
@Component({
selector: 'app-user-detail',
standalone: true,
template: `
@if (userDetail.isLoading()) {
<div class="spinner-border" role="status">
<span class="visually-hidden">Chargement...</span>
</div>
} @else if (userDetail.value(); as user) {
<h2>{{ user.name }}</h2>
<p>Rôle : {{ user.role }}</p>
<p>Créé le : {{ user.createdAt }}</p>
}
`
})
export class UserDetailComponent {
// Signal input : reçoit l'ID depuis le composant parent
userId = input.required<number>();
// La requête se relance automatiquement quand userId() change
// httpResource accepte une fonction qui retourne l'URL (réactive)
userDetail = httpResource<UserDetail>(() => `/api/users/${this.userId()}`);
}
Options avancées : headers, params, méthode POST
// httpResource avec options complètes
import { httpResource } from '@angular/common/http';
import { signal } from '@angular/core';
// Signal pour la recherche (déclenchera une nouvelle requête à chaque changement)
const searchQuery = signal('');
const page = signal(1);
// httpResource avec URL dynamique + headers personnalisés
const searchResults = httpResource(() => ({
// URL construite depuis les Signals (réactive)
url: '/api/products/search',
// Query params générés dynamiquement
params: {
q: searchQuery(),
page: String(page()),
size: '20'
},
// Headers personnalisés (ex: token de pagination)
headers: {
'X-Api-Version': '2'
}
}));
// Pour POST / PUT / DELETE : utiliser httpResource avec method
const createUser = httpResource(() => ({
url: '/api/users',
method: 'POST',
body: { name: 'Alice', role: 'admin' }
}));
Comparaison HttpClient classique vs httpResource()
| Critère | HttpClient + Observable | httpResource() |
|---|---|---|
| Lignes de code (CRUD simple) | 15-30 lignes | 1-5 lignes |
| Gestion loading/error | Manuelle (BehaviorSubject) |
Automatique via Signals |
| Re-fetch automatique | Non (manuel avec switchMap) |
Oui (réactif aux Signals) |
| Cas d'usage complexes (retry, merge, concat) | Puissant (RxJS) | Limité |
| Learning curve | Élevée (RxJS) | Faible |
httpResource() est idéal pour les composants qui affichent des données (GET). Pour les scénarios complexes — polling, retry avec backoff, requêtes parallèles ou chaînées — continuez d'utiliser HttpClient avec RxJS. Les deux coexistent sans problème dans la même application.
Template diagnostics améliorés
Angular 21 apporte une refonte majeure du système de diagnostics de templates. L'objectif : transformer les messages d'erreur cryptiques du compilateur en messages actionnables et pédagogiques, avec des suggestions de correction intégrées.
Avant v21 : erreurs peu exploitables
Avant Angular 21, une erreur de binding dans un template produisait des messages du type :
// Erreur Angular 20 (peu informative)
// TS2339: Property 'userName' does not exist on type 'MyComponent'.
// Il fallait deviner :
// - S'agit-il d'une faute de frappe ?
// - La propriété est-elle publique ?
// - Est-ce un signal ou une propriété classique ?
Avec Angular 21 : messages contextuels + suggestions
// Angular 21 : diagnostic enrichi avec suggestion automatique
// NG8002: Can't bind to 'userName' since it isn't a known property of 'AppComponent'.
//
// Did you mean 'username'? (found similar property with different casing)
//
// If this is a custom element, add it to the '@NgModule's 'CUSTOM_ELEMENTS_SCHEMA'
// or use 'NO_ERRORS_SCHEMA' in your NgModule.
//
// 5 | <p>{{ userName }}</p>
// ^^^^^^^^
// Fix: rename to 'username'
Nouveau : strict template type checking étendu
Angular 21 étend le strict template type checking à de nouveaux cas, notamment les directives structurelles personnalisées et les Signal inputs.
// tsconfig.json — activer le strict template checking complet
{
"angularCompilerOptions": {
// Active la vérification stricte des templates (recommandé depuis v12)
"strictTemplates": true,
// NOUVEAU en v21 : vérifie les types dans les directives structurelles
"strictInputAccessModifiers": true,
// NOUVEAU en v21 : avertit si un Signal est utilisé sans être appelé comme fonction
"strictSignalUsage": true
}
}
Diagnostic "Signal appelé sans parenthèses"
Une erreur très courante chez les développeurs qui découvrent les Signals : oublier les parenthèses lors de la lecture d'un Signal dans un template. Angular 21 détecte maintenant ce pattern et propose une correction immédiate.
// ❌ AVANT : Angular affichait [object Signal] dans le template
// (aucun avertissement du compilateur en v20)
@Component({
template: `<p>{{ count }}</p>` // Affiche '[object Signal]' au lieu de la valeur !
})
export class BadComponent {
count = signal(42); // Signal non appelé dans le template
}
// ✅ AVEC Angular 21 strict template checking :
// NG8XXX: Signal 'count' must be called as a function in templates.
// Use '{{ count() }}' instead of '{{ count }}'.
//
// Fix automatique disponible via ng lint --fix
// ✅ Code correct
@Component({
template: `<p>{{ count() }}</p>` // count() appelé correctement
})
export class GoodComponent {
count = signal(42);
}
Diagnostics pour les inputs requis manquants
// Composant avec un input requis
@Component({
selector: 'app-card',
standalone: true,
template: `<div class="card">{{ title() }}</div>`
})
export class CardComponent {
// input.required() : Angular 21 vérifie que le parent fournit bien cette valeur
title = input.required<string>();
}
// ❌ Utilisation incorrecte dans le template parent (Angular 21 détecte + suggère)
// <app-card></app-card>
// NG8008: Required input 'title' from component CardComponent must be specified.
// The component selector is 'app-card'.
// ✅ Utilisation correcte
// <app-card title="Mon titre"></app-card>
strictTemplates: true, les erreurs de template sont maintenant détectées à la compilation avec des suggestions de correction, avant même que le navigateur soit ouvert.
HMR natif sans configuration
Le Hot Module Replacement (HMR) permet de mettre à jour les composants, styles et templates dans le navigateur sans recharger la page. L'état de l'application est conservé — un gain de temps considérable lors du développement.
Angular 21 active le HMR par défaut pour les nouveaux projets et les projets qui utilisent l'application builder (esbuild). Aucune configuration supplémentaire n'est requise.
Avant Angular 21 : live reload vs HMR
// Angular 20 : HMR activable manuellement via flag CLI
// angular.json
{
"architect": {
"serve": {
"options": {
// Nécessitait une activation explicite
"hmr": true
}
}
}
}
// Ou via la ligne de commande
// ng serve --hmr
Angular 21 : HMR activé nativement
// angular.json Angular 21 — HMR actif par défaut (plus besoin du flag)
{
"architect": {
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
// hmr: true est maintenant le défaut implicite
// Vous pouvez le désactiver explicitement si nécessaire
"hmr": false // Pour revenir au live reload classique
}
}
}
}
Ce qui est mis à jour en HMR sans rechargement de page
- Templates HTML des composants : modifications visuelles instantanées
- Styles CSS / SCSS des composants : changements de couleurs, marges, etc.
- Styles globaux (
styles.scss) : thèmes, variables CSS - Logique TypeScript dans les méthodes : les handlers et computed sont mis à jour
- État du composant : conservé entre les mises à jour (Signals inclus)
Ce qui déclenche encore un rechargement complet
// Ces modifications nécessitent encore un rechargement de page complet :
// 1. Changement de décorateur @Component (selector, changeDetection...)
// 2. Ajout / suppression d'imports dans le tableau imports: []
// 3. Modifications dans app.config.ts (providers globaux)
// 4. Changements dans les fichiers de routing
// 5. Modifications des classes d'injection (providers, tokens)
// Angular 21 affiche un message dans la console quand un rechargement complet est nécessaire :
// [HMR] Full reload required: decorator metadata changed in MyComponent
Mesurer le gain de productivité
// Comparaison live reload vs HMR
// Workflow sans HMR (live reload classique) :
// 1. Modifier un style dans button.component.scss
// 2. Attendre la recompilation (2-4s avec esbuild)
// 3. Rechargement complet de la page
// 4. Re-naviguer vers l'écran en cours de développement (2-5s)
// Total : 5-10s par modification
// Workflow avec HMR Angular 21 :
// 1. Modifier un style dans button.component.scss
// 2. Recompilation partielle du composant uniquement (~300ms)
// 3. Mise à jour dans le navigateur sans rechargement de page
// Total : <1s par modification
// Sur une session de développement de 4h avec 200 modifications :
// Sans HMR : 200 × 7s = ~23 minutes perdues
// Avec HMR : 200 × 0.5s = ~2 minutes
Zoneless : enfin stable en v21
Le mode zoneless (sans Zone.js) est officiellement stable dans Angular 21. C'est l'aboutissement d'un chantier ouvert en v16 avec l'introduction des Signals. Sans Zone.js, Angular ne surveille plus tous les événements asynchrones pour déclencher la détection de changements — c'est l'application qui pilote explicitement ce qui doit être mis à jour.
Pourquoi se débarrasser de Zone.js ?
Zone.js est la bibliothèque qui permettait à Angular d'être "magiquement" réactif. Elle patche toutes les APIs asynchrones du navigateur (setTimeout, Promise, fetch, événements DOM…) pour notifier Angular après chaque opération. Ce mécanisme a un coût :
- +36 Ko ajoutés au bundle (après compression : ~13 Ko)
- Overhead à chaque opération asynchrone (patch des APIs natives)
- Difficile à déboguer quand Zone.js interfère avec des libs tierces
- Incompatible avec certains environnements (Web Workers, SSR optimisé)
Activer le mode zoneless en Angular 21
// app.config.ts — activer zoneless (stable depuis Angular 21)
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideZonelessChangeDetection } from '@angular/core'; // Import depuis @angular/core
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
// Remplace provideZone() : active la détection de changement zoneless
// Zone.js n'est plus nécessaire dans polyfills
provideZonelessChangeDetection()
]
};
// angular.json — supprimer Zone.js des polyfills
{
"projects": {
"my-app": {
"architect": {
"build": {
"options": {
// Retirer 'zone.js' de cette liste
"polyfills": [
// "zone.js", <-- Supprimer cette ligne
"@angular/localize/init"
]
}
}
}
}
}
}
Composant compatible zoneless : utiliser les Signals
// ✅ Composant 100% zoneless-compatible
// Toutes les propriétés réactives utilisent des Signals
import { Component, signal, computed, inject } from '@angular/core';
import { httpResource } from '@angular/common/http';
interface Product {
id: number;
name: string;
price: number;
}
@Component({
selector: 'app-product-list',
standalone: true,
template: `
<input (input)="search.set($event.target.value)" placeholder="Rechercher..." />
<p>{{ filteredCount() }} produit(s) trouvé(s)</p>
@for (product of filteredProducts(); track product.id) {
<div class="card p-2 mb-2">
<strong>{{ product.name }}</strong> — {{ product.price }}€
</div>
}
`
})
export class ProductListComponent {
// Signal pour la recherche (mis à jour par l'utilisateur)
search = signal('');
// httpResource : réactif, signal-based, compatible zoneless
products = httpResource<Product[]>('/api/products');
// computed() : recalculé automatiquement quand search() ou products.value() change
filteredProducts = computed(() => {
const term = this.search().toLowerCase();
const all = this.products.value() ?? [];
// Filtrage côté client en temps réel
return all.filter(p => p.name.toLowerCase().includes(term));
});
// computed() dérivé pour afficher le compteur
filteredCount = computed(() => this.filteredProducts().length);
}
Migration progressive : composant par composant
// Stratégie recommandée : migrer progressivement
// Étape 1 — Activer zoneless dans app.config.ts (tout en gardant zone.js pour l'instant)
// Étape 2 — Identifier les composants qui utilisent encore ChangeDetectorRef.markForCheck()
// Étape 3 — Remplacer les EventEmitter par des Signals output()
// Étape 4 — Remplacer les @Input() par des signal input()
// Étape 5 — Retirer zone.js des polyfills
// ❌ Pattern incompatible zoneless (déclenche pas de mise à jour sans Zone.js)
export class OldComponent {
title: string = ''; // Propriété classique, pas un Signal
loadTitle() {
setTimeout(() => {
this.title = 'Chargé'; // Zone.js était nécessaire pour détecter ça
}, 1000);
}
}
// ✅ Pattern compatible zoneless
export class NewComponent {
title = signal(''); // Signal : Angular sait exactement quand ça change
loadTitle() {
setTimeout(() => {
this.title.set('Chargé'); // Signal.set() notifie Angular directement
}, 1000);
}
}
CLI et esbuild : performances de build record
Angular 21 continue d'améliorer les performances de build avec esbuild comme bundler par défaut. Mais la vraie nouveauté de cette version est la migration automatique des anciens projets Webpack vers esbuild, et plusieurs optimisations de l'incrémental build.
État de l'application builder en Angular 21
Depuis Angular 17, tous les nouveaux projets utilisent @angular-devkit/build-angular:application (alias esbuild) à la place de l'ancien browser builder (Webpack). Angular 21 ajoute :
- Migrations automatiques :
ng updatemigre automatiquement la configurationangular.jsondes anciens projets vers le builder esbuild - Build incrémental amélioré : seuls les modules modifiés sont recompilés (cache granulaire au niveau du fichier)
- Optimisation des assets : compression Brotli des assets JS/CSS activée par défaut en mode production
Comparer les vitesses de build
| Opération | Webpack (ng 16) | esbuild (ng 17-20) | esbuild (ng 21) |
|---|---|---|---|
| Build initial (projet moyen) | 45-90s | 8-15s | 6-12s |
| Rebuild incrémental | 5-15s | 500ms-2s | 200ms-1s |
| Build production (optimisé) | 90-180s | 15-30s | 12-25s |
| Taille bundle (app moyenne) | 450-800 Ko | 200-350 Ko | 180-320 Ko |
Migrer un ancien projet vers esbuild
# Angular 21 : migration automatique via ng update
ng update @angular/cli @angular/core
# Si la migration automatique ne s'applique pas, exécuter le schematic manuellement
ng update @angular/cli --migrate-only use-application-builder
# Ce schematic modifie automatiquement angular.json :
# - Remplace "builder": "@angular-devkit/build-angular:browser"
# - par "builder": "@angular-devkit/build-angular:application"
# - Adapte les options incompatibles (stylePreprocessorOptions, etc.)
// angular.json après migration — application builder (esbuild)
{
"architect": {
"build": {
// Nouveau builder esbuild (remplace @angular-devkit/build-angular:browser)
"builder": "@angular-devkit/build-angular:application",
"options": {
// Point d'entrée de l'application (nouveau format)
"browser": "src/main.ts",
// Génère les server-side rendering bundles si SSR activé
"server": "src/main.server.ts",
// Optimisations production : minification, tree-shaking, code splitting
"optimization": true,
// Source maps désactivées en prod (activer si besoin de debugging)
"sourceMap": false,
// Budgets de performance : alerte si bundle trop lourd
"budgets": [
{
"type": "initial",
"maximumWarning": "500kB",
"maximumError": "1MB"
}
]
}
}
}
}
Nouvelles options CLI Angular 21
# Générer un composant standalone (défaut depuis v19, inchangé)
ng generate component my-component --standalone
# NOUVEAU : générer une ressource HTTP typée (scaffold httpResource)
ng generate resource users --type User
# Ce dernier crée :
# - users.resource.ts : service avec httpResource() pré-configuré
# - user.model.ts : interface TypeScript User
# - users.resource.spec.ts : tests unitaires
# Analyser la taille du bundle (utile pour optimiser)
ng build --stats-json
npx webpack-bundle-analyzer dist/my-app/stats.json
Migrer de v20 à v21 : guide pas à pas
La migration d'Angular 20 vers Angular 21 est conçue pour être aussi transparente que possible. L'équipe Angular maintient une politique stricte de compatibilité descendante sur les versions mineures et de migrations automatiques pour les changements breaking.
Étape 1 : vérifier les prérequis
# Vérifier la version Node.js (Angular 21 requiert Node.js 18.19+ ou 20.9+)
node --version
# v20.11.0 ✓
# Vérifier la version npm (8.0+ recommandé)
npm --version
# 10.2.4 ✓
# Vérifier la version Angular CLI actuelle
ng version
# Angular CLI: 20.x.x → à mettre à jour
Étape 2 : mettre à jour Angular CLI et Core
# Mettre à jour Angular CLI globalement (optionnel mais recommandé)
npm install -g @angular/cli@21
# Mettre à jour le projet via ng update (méthode officielle recommandée)
# Cette commande applique les migrations automatiques
ng update @angular/core@21 @angular/cli@21
# Si vous utilisez Angular Material
ng update @angular/material@21
# Si vous utilisez Angular CDK
ng update @angular/cdk@21
Étape 3 : vérifier les breaking changes
Angular 21 comporte peu de breaking changes mais voici les points d'attention :
// 1. API httpResource() stabilisée : renommer les imports (si utilisé en Developer Preview)
// AVANT (v20 Developer Preview) :
import { httpResource } from '@angular/common/http/experimental';
// APRÈS (v21 Stable) :
import { httpResource } from '@angular/common/http'; // Import direct sans /experimental
// 2. provideZonelessChangeDetection() déplacé
// AVANT (v20) :
import { provideZonelessChangeDetection } from '@angular/core/experimental';
// APRÈS (v21) :
import { provideZonelessChangeDetection } from '@angular/core'; // Stable
// 3. Type narrowing des Signal inputs : peut révéler des erreurs TypeScript cachées
// Si vous avez des erreurs TS après mise à jour, vérifiez les composants
// avec des inputs de type union (string | null, User | undefined, etc.)
Étape 4 : appliquer les migrations automatiques
# ng update applique automatiquement les schematics de migration :
# - Migration vers le builder esbuild (si encore sur Webpack)
# - Renommage des imports expérimentaux vers stable
# - Mise à jour des décorateurs dépréciés
# Vérifier les migrations appliquées dans la sortie de ng update :
# ✔ Migration '@angular/core:signal-input-required-usage' applied
# ✔ Migration '@angular/cli:use-application-builder' applied
# ✔ Migration '@angular/core:http-resource-import' applied
# Inspecter les changements avant commit :
git diff --stat
Étape 5 : tester et valider
# Lancer les tests unitaires pour vérifier la régression
ng test --no-watch --no-progress
# Lancer le build production pour vérifier la compilation complète
ng build --configuration production
# Démarrer le serveur de développement (HMR actif par défaut)
ng serve
# Si des tests échouent après migration, chercher dans ces zones :
# - Mocks de HttpClient (API légèrement modifiée)
# - Tests de composants avec des inputs requis (strictInputAccessModifiers)
# - Imports depuis @angular/*/experimental (devenus stables, changer le path)
Checklist de migration complète
- Node.js 18.19+ ou 20.9+ installé
-
ng update @angular/core@21 @angular/cli@21exécuté - Imports
/experimentalrenommés vers chemins stables - Builder esbuild activé (migration automatique ou manuelle)
-
ng testsans régression -
ng build --configuration productionsans erreur - Vérification des budgets de bundle (taille ne doit pas exploser)
- HMR vérifié en développement (
ng serve)
--force de ng update uniquement en dernier recours. Préférez attendre les mises à jour des dépendances ou utiliser les overrides npm pour débloquer temporairement la situation.
Conclusion et perspectives Angular 22
Angular 21 est une version de consolidation majeure. Elle ne révolutionne pas les fondamentaux — c'est la v16 qui l'a fait avec les Signals — mais elle rend stable et production-ready ce qui était encore expérimental : httpResource(), provideZonelessChangeDetection(), et le HMR natif. C'est la version qui marque la fin de la transition et le début d'une nouvelle ère Angular pleinement signal-based.
Pour aller plus loin immédiatement : activez strictTemplates: true dans votre tsconfig.json, migrez vos composants les plus simples vers httpResource() pour vous faire la main, et testez le mode zoneless sur un projet secondaire avant de l'adopter en production. Angular 22 devrait apporter des améliorations sur le SSR (Server-Side Rendering) avec le nouveau moteur de rendu basé sur les Signals et des optimisations supplémentaires du compilateur de templates.
computed(), la migration vers le mode zoneless et httpResource() sera quasi-immédiate. Commencez dès maintenant à identifier les composants encore basés sur des propriétés classiques — c'est votre roadmap de modernisation Angular.