Angular v22 est une version majeure, mais elle ne va pas vous gâcher la semaine. Au lieu de vous balancer une pile de nouvelles API, l'équipe a pris une longue liste de fonctionnalités expérimentales pour les marquer stables, puis a changé quelques comportements par défaut pour coller à la façon dont on écrit Angular aujourd'hui. Si vous êtes déjà passé aux signals, aux composants standalone et au nouveau control flow, une bonne partie de la v22 ressemble moins à un apprentissage qu'au framework qui rattrape votre code.
Passons en revue tout ça, section par section, en commençant par le changement qui touche chaque composant que vous écrirez un jour : une nouvelle stratégie de détection de changements par défaut.
Angular v22 en un coup d'œil
- Date de sortie : 3 juin 2026
- OnPush devient la stratégie de détection de changements par défaut pour chaque composant
- Les Signal Forms, l'API resource et Angular Aria sont stables
- Vitest remplace Karma et Jasmine comme exécuteur de tests par défaut
- L'hydratation incrémentale est activée par défaut pour les applications rendues côté serveur
- HttpClient utilise l'API Fetch à la place de XMLHttpRequest par défaut
- Nouveau décorateur
@ServiceetinjectAsync()pour l'injection de dépendances - Les prérequis changent : TypeScript v6 et Node v22 sont désormais obligatoires
OnPush devient la stratégie de détection de changements par défaut
C'est celui auquel il faut prêter attention. Pendant des années, chaque composant que vous créiez tournait avec
l'ancienne stratégie « tout vérifier, tout le temps », qui réévalue les liaisons d'un composant à presque chaque
événement, n'importe où dans l'application. À partir de la v22, un composant qui ne déclare pas de stratégie
reçoit
ChangeDetectionStrategy.OnPush
à la place. Angular ne le revérifie que lorsque ses inputs changent, qu'un signal qu'il lit se met à jour, ou
qu'un
événement se déclenche à l'intérieur. Rien de plus.
Si votre application s'appuie déjà sur les signals et des inputs immuables, vous gagnez moins de vérifications inutiles et un rendu plus rapide gratuitement, sans toucher une ligne. Le piège, c'est l'ancien code qui mute discrètement des objets sur place : ces vues peuvent cesser de se mettre à jour. La bonne nouvelle, c'est que la migration s'en charge pour vous en insérant la stratégie de repli partout où c'est nécessaire :
import { ChangeDetectionStrategy, Component } from '@angular/core';
// Nouveau comportement en v22 : OnPush est implicite si vous n'écrivez rien.
@Component({
selector: 'app-product',
templateUrl: './product.html'
})
export class ProductComponent {}
// Revenez à l'ancien comportement « tout vérifier » quand vous en avez besoin.
@Component({
selector: 'app-legacy',
changeDetection: ChangeDetectionStrategy.Eager,
templateUrl: './legacy.html'
})
export class LegacyComponent {}
Notez la valeur renommée : l'ancien défaut s'appelle désormais ChangeDetectionStrategy.Eager.
Quand vous lancez ng update,
le schematic l'applique à chaque composant qui n'avait pas déjà défini de stratégie, donc rien ne change côté
comportement le premier jour. Ensuite, vous pouvez le retirer composant par composant, en vérifiant que chaque vue
fonctionne toujours sous OnPush au fur et à mesure.
Les Signal Forms sont stables
Les Signal Forms abandonnent enfin l'étiquette expérimentale en v22. Toute l'API repose sur les signals, ce qui
veut dire que la valeur du formulaire, son état de validation, et même le fait qu'un champ ait été touché, sont
des
signals réactifs que vous lisez directement depuis un template ou un computed. Pas de FormGroup, pas d'arbre de
FormControl à
surveiller
et à garder synchronisé avec votre modèle.
Vous partez d'un modèle objet tout simple enveloppé dans signal(), puis vous
décrivez
sa validation avec la fonction form() et un schéma :
import { Component, signal } from '@angular/core';
import { form, required, email, minLength } from '@angular/forms/signals';
@Component({
selector: 'app-login',
templateUrl: './login.html'
})
export class LoginComponent {
model = signal({ email: '', password: '' });
loginForm = form(this.model, (path) => {
required(path.email);
email(path.email);
required(path.password);
minLength(path.password, 8);
});
submit() {
if (this.loginForm().valid()) {
console.log(this.model());
}
}
}
Dans le template, vous liez les champs avec la directive Field et vous lisez les
erreurs directement depuis le signal du formulaire :
<form (submit)="submit()">
<input type="email" [field]="loginForm.email" />
<input type="password" [field]="loginForm.password" />
<p>{{ loginForm.password().getError('minLength') }}</p>
<button [disabled]="loginForm().invalid()">Se connecter</button>
</form>
La version stable ajoute les détails que vous utilisez vraiment au quotidien : les validateurs minDate() et maxDate(), une méthode
getError() qui
restreint
correctement les types, le debounce sur le blur et sur les validateurs asynchrones, et une sortie touch() qui remplace le
maladroit drapeau touched mutable d'avant. Et si vous avez des contrôles personnalisés écrits à l'ancienne avec
ControlValueAccessor,
ils
continuent de fonctionner avec les Signal Forms, donc pas besoin de tout réécrire d'un coup pour adopter tout ça.
L'API resource est prête pour la production
resource(), rxResource() et httpResource() sont stables
à partir de la v22. Leur intérêt, c'est de garder les données asynchrones à l'intérieur du graphe de signals, pour
que vous arrêtiez de recoudre RxJS et les signals à la main à chaque fois que vous récupérez quelque chose. Un
httpResource recharge
tout seul dès qu'un signal dont il dépend change, et il vous donne loading, error et value sous forme de signals
que vous lisez directement.
import { Component, signal } from '@angular/core';
import { httpResource } from '@angular/common/http';
@Component({
selector: 'app-user',
templateUrl: './user.html'
})
export class UserComponent {
userId = signal(1);
// Recharge automatiquement dès que userId() change.
user = httpResource(() => `/api/users/${this.userId()}`);
}
@if (user.isLoading()) {
<p>Chargement...</p>
} @else if (user.error()) {
<p>Une erreur est survenue.</p>
} @else {
<h2>{{ user.value()?.name }}</h2>
}
Deux choses méritent d'être soulignées dans la version stable. Il y a une nouvelle méthode chain() qui rend
l'enchaînement du résultat d'une resource vers la suivante indolore, avec la propagation automatique des états de
chargement et d'erreur. Et si vous passez un id dans les options, une
resource résolue pendant le rendu côté serveur est mise en cache et s'affiche instantanément côté client, sans
second clignotement de chargement le temps qu'elle se recharge.
Le décorateur @Service et injectAsync()
Déclarer un service singleton a toujours voulu dire taper @Injectable({ providedIn: 'root' }).
Le nouveau décorateur @Service()
dit exactement la même chose avec beaucoup moins de cérémonie. Par défaut, il vous donne un singleton
tree-shakeable à l'échelle de l'application, comme l'Injectable fourni à la racine, sauf que cette fois le nom dit
ce que c'est :
import { Service, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Service()
export class ProductService {
private http = inject(HttpClient);
getProducts() {
return this.http.get('/api/products');
}
}
Une chose à savoir : un @Service
récupère ses dépendances via inject() plutôt que par le
constructeur, ce qui l'aligne sur la façon dont le reste d'Angular moderne fonctionne déjà.
Juste à côté se trouve injectAsync(),
qui charge un service en lazy via un import dynamique, pour que son code parte dans son propre chunk et ne se
télécharge qu'à la première fois où quelqu'un en a besoin. Si vous préférez ne pas payer ce coût à la demande,
vous
pouvez le faire précharger discrètement pendant que le navigateur est inactif :
import { injectAsync, onIdle } from '@angular/core';
export class CheckoutComponent {
private heavyService = injectAsync(
() => import('./pricing.service').then((m) => m.PricingService),
{ prefetch: onIdle }
);
}
Vitest remplace Karma et Jasmine
Les nouveaux projets en v22 démarrent avec Vitest comme exécuteur de tests. Karma est sur le départ depuis un moment, et Jasmine n'est plus ce que vous obtenez par défaut. Vitest est rapide, tourne en mode watch sans rien demander, et réutilise la même configuration de build que votre application, donc vous ne maintenez pas une chaîne d'outils séparée rien que pour les tests.
Les projets existants continuent de tourner tels quels, et la CLI vous donne des schematics pour faire le saut. Pour convertir une configuration Karma et Jasmine, lancez :
ng generate @schematics/angular:migrate-karma-to-vitest
Inquiet pour vos tests asynchrones ? Si vous comptez sur fakeAsync, flush ou waitForAsync, ces
utilitaires Zone.js tournent désormais sous Vitest grâce à un patch dédié, donc il n'y a rien à réécrire. Deux
nouveaux flags aident aussi : --isolate
lance les tests dans des processus séparés, et --quiet garde le bruit du
build hors de votre sortie.
L'hydratation incrémentale par défaut
Si vous rendez côté serveur, l'hydratation incrémentale est activée par défaut maintenant. Plutôt que de réveiller
toute la page d'un coup, Angular hydrate des morceaux à mesure qu'ils entrent dans la vue ou que l'utilisateur
interagit avec eux. Résultat : moins de JavaScript exécuté au premier chargement et de meilleurs chiffres
d'interaction sur les pages lourdes. L'ancien appel withIncrementalHydration()
est déprécié, puisque vous obtenez le comportement gratuitement. Tant qu'on y est : provideServerRendering() a
récupéré une option maxResponseBodySize qui
vaut
1 Mo par défaut.
HttpClient passe à l'API Fetch
Sous le capot, HttpClient parle désormais à l'API Fetch du navigateur au lieu de XMLHttpRequest. Le withFetch() que vous
activiez avant est simplement le défaut, et si quelque chose dans votre stack a encore besoin de l'ancien
transport, vous pouvez revenir en arrière avec withXhr(). Fetch garde
Angular aligné sur la plateforme moderne, se comporte mieux en dehors du navigateur, et s'entend bien avec le
rendu
côté serveur.
import { provideHttpClient, withXhr } from '@angular/common/http';
export const appConfig = {
providers: [
// Fetch est désormais le défaut ; ne revenez en arrière que si vous y êtes obligé.
provideHttpClient(withXhr())
]
};
Améliorations des templates et du compilateur
Les templates continuent de gagner en capacités et en rigueur en même temps. Le changement que vous remarquerez en premier, c'est de pouvoir glisser un commentaire HTML à l'intérieur d'une balise ouvrante, ce qui est vraiment pratique quand vous voulez annoter ou désactiver temporairement un seul attribut sur un élément à rallonge :
<input
type="text"
[value]="name()"
<!-- [disabled]="locked()" temporairement désactivé -->
(input)="onInput($event)"
/>
Au-delà de ça, le compilateur est devenu plus malin sur l'optional chaining avec des null guards automatiques, a
ajouté des vérifications exhaustives de @switch, a donné à @defer (on idle(500ms)) un
timeout pour qu'il ne puisse pas attendre indéfiniment, et repère maintenant les noms d'input ou d'output en
double
et les boucles @for
cassées à la compilation plutôt que de les laisser filer jusqu'à l'exécution. Les strict templates sont aussi
activés par défaut, et la migration ajoute strictTemplates: false pour
tout projet qui n'est pas encore prêt pour ça.
Intégration de l'IA avec WebMCP
La v22 tente aussi un pari expérimental sur l'IA avec WebMCP, un protocole navigateur qui permet aux agents IA d'appeler des fonctionnalités dans votre application en cours d'exécution. La partie qui a attiré mon attention, c'est l'exposition automatique des formulaires : vous branchez les providers, vous taguez un Signal Form, et Angular lit le modèle du formulaire, en construit un schéma JSON, et rend le tout appelable par un agent. L'agent récupère même les erreurs de validation et peut corriger lui-même son entrée.
import { provideExperimentalWebMcpForms } from '@angular/forms/signals';
export const appConfig = {
providers: [
provideExperimentalWebMcpForms()
]
};
L'équipe a aussi publié des skills Angular officiels que vous pouvez greffer sur vos outils de codage IA avec
npx skills add https://github.com/angular/skills,
et vos graphes de signals et d'injection de dépendances apparaissent maintenant comme des extensions des Chrome
DevTools. Tout ça est expérimental, alors ne soyez pas surpris quand l'API bougera avant de se stabiliser.
Angular Aria est stable
Le paquet @angular/aria
est passé de l'aperçu développeur à la production. Il vous donne des briques accessibles, des choses comme la
navigation au clavier et le câblage ARIA que personne n'aime écrire à la main, que vous composez dans vos propres
composants, et il se branche directement sur les Signal Forms. Si vous maintenez un design system en interne,
c'est
la base d'accessibilité sans avoir à tirer toute une bibliothèque de composants pour l'obtenir.
Renforcement de la sécurité
Il y a une vraie histoire de sécurité ici aussi. La v22 a ajouté des protections contre le server-side request
forgery (SSRF) autour de la façon dont platform-server initialise
location et document, a resserré la sanitization des attributs href dynamiques sur les
ancres SVG, et a durci les données de locale contre la pollution de prototype. Le transfer cache ignore désormais
par défaut toute requête qui transporte des cookies ou utilise withCredentials, pour que
les réponses sensibles ne soient pas figées dans la page. Rien de tout ça ne vous demande quoi que ce soit, mais
c'est agréable de savoir que votre application ressort de la mise à jour un peu plus sûre.
Les ruptures à surveiller
Les migrations couvrent l'essentiel de la v22, mais une poignée de changements valent la peine d'y jeter un œil vous-même avant de livrer :
- TypeScript v6 et Node v22 sont requis. TypeScript 5.9 et Node 20 ne sont plus pris en charge.
- OnPush est le défaut. Les vues qui reposaient sur la mutation d'objets sur place peuvent
cesser de se mettre à jour tant que vous ne les basculez pas sur
Eagerou ne passez pas aux signals. - L'héritage des paramètres du routeur change.
paramsInheritanceStrategyvaut maintenant'always'par défaut, et celui-ci n'est pas migré automatiquement. - L'optional chaining renvoie
undefinedau lieu denulldans les templates ; une migration peut préserver l'ancien comportement si vous en dépendez.
Comment passer à Angular v22
Avant toute chose, mettez-vous sur Node v22 et assurez-vous que le projet est déjà sur Angular v21, car la CLI ne saute qu'une version majeure à la fois. Ensuite, lancez la mise à jour :
# Vérifiez votre version actuelle
ng version
# Mettez à jour le framework principal et la CLI vers la v22
ng update @angular/core@22 @angular/cli@22
Les schematics mettent à jour TypeScript, insèrent ChangeDetectionStrategy.Eager
là où c'est nécessaire, et nettoient les API dépréciées au passage. Une fois que c'est fait, lancez vos tests,
gardez un œil sur les vues qui ont cessé de se mettre à jour sous OnPush, et revérifiez que tout code qui tirait
des
paramètres du routeur depuis une route parente fait toujours ce que vous attendez. Quand tout est au vert,
commencez à retirer ces marqueurs Eager pour profiter
réellement du nouveau défaut.
Conclusion
La v22 n'est pas vraiment une histoire de nouveaux gadgets. C'est une histoire de rendre la façon moderne et
centrée sur les signals de construire Angular la voie de moindre résistance. OnPush par défaut vous récompense
d'écrire du code réactif. Les Signal Forms stables et l'API resource enlèvent les dernières raisons de revenir aux
anciens patterns. Vitest, l'hydratation incrémentale et le HttpClient basé sur Fetch modernisent discrètement
l'outillage sous vos pieds. Et @Service rogne un peu de
boilerplate sur chaque service que vous écrivez.
La mise à jour est en grande partie automatisée, les ruptures sont gérables, et ce que vous en retirez, c'est une application plus rapide avec un code plus propre. Si vous écrivez déjà des signals et des composants standalone, la v22 va vous sembler familière. Quand vous serez prêt à creuser les détails, la page de sortie officielle d'Angular v22 et le guide de mise à jour Angular sont les deux liens à garder ouverts.