Angular 19 Zoneless : Detection de Changements sans Zone.js et Gains de Performance
Guide complet sur la detection de changements sans Zone.js dans Angular 19 et 20. Configuration de provideZonelessChangeDetection, migration vers les signals, SSR sans Zone.js, benchmarks de performance et pieges a eviter lors de la migration.

La suppression de Zone.js represente l'un des changements architecturaux les plus attendus dans l'ecosysteme Angular. Depuis sa creation, Angular s'appuie sur cette bibliotheque pour detecter automatiquement les modifications d'etat et mettre a jour le DOM. Zone.js intercepte plus de 130 API asynchrones du navigateur -- setTimeout, Promise.then, addEventListener -- afin de declencher des cycles de detection de changements apres chaque operation asynchrone. Cette approche, bien que transparente pour les developpeurs, genere un cout considerable en termes de performance : un bundle supplementaire de 33 Ko, des cycles de detection superflus et des stack traces polluees par des frames intermediaires.
Angular 19 introduit le mode zoneless en version experimentale, permettant aux applications de fonctionner sans Zone.js en s'appuyant exclusivement sur les signals et les notifications explicites. Angular 20 stabilise cette API, et Angular 21 en fait le comportement par defaut pour les nouveaux projets. Cette transition marque un tournant dans la maniere dont Angular gere la reactivite et la detection de changements.
Angular 19 propose provideExperimentalZonelessChangeDetection() en statut experimental. Angular 20 stabilise l'API sous le nom provideZonelessChangeDetection(). Angular 21, prevu pour fin 2026, configure le mode zoneless par defaut dans les nouveaux projets generes via le CLI. Les applications existantes conservent Zone.js jusqu'a une migration explicite.
Fonctionnement de la detection de changements avec Zone.js
Pour comprendre les benefices du mode zoneless, il convient d'analyser le mecanisme que Zone.js implemente. Au demarrage de l'application, Zone.js applique des patches (monkey-patching) sur les API asynchrones natives du navigateur. Chaque appel a setTimeout, chaque resolution de Promise, chaque evenement du DOM declenche une notification vers Angular, qui execute alors un cycle complet de detection de changements sur l'arbre de composants.
Dans une application typique, une simple interaction utilisateur -- un clic sur un bouton, par exemple -- peut provoquer entre 150 et 300 cycles de detection de changements. La majorite de ces cycles sont superflus : ils parcourent l'integralite de l'arbre de composants alors que seul un fragment du DOM necessite une mise a jour.
import { ApplicationConfig } from '@angular/core';
import { provideZoneChangeDetection } from '@angular/core';
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
// Zone.js patches ~130+ browser APIs
// Every async callback triggers change detection
]
};L'option eventCoalescing: true, introduite dans Angular 14, regroupe les evenements DOM qui surviennent dans le meme tour de la boucle d'evenements en un seul cycle de detection. Ce mecanisme reduit le nombre de cycles, mais ne resout pas le probleme fondamental : Zone.js continue d'intercepter l'ensemble des API asynchrones, y compris celles qui ne modifient aucun etat de l'interface.
Activer le mode zoneless dans Angular 19 et 20
La configuration du mode zoneless differe selon la version d'Angular utilisee. Dans Angular 19, l'API porte le prefixe Experimental pour signaler son statut. A partir d'Angular 20, le prefixe disparait et l'API devient stable.
import { ApplicationConfig } from '@angular/core';
import { provideExperimentalZonelessChangeDetection } from '@angular/core';
export const appConfig: ApplicationConfig = {
providers: [
provideExperimentalZonelessChangeDetection(),
// No more Zone.js patching
]
};import { ApplicationConfig } from '@angular/core';
import { provideZonelessChangeDetection } from '@angular/core';
export const appConfig: ApplicationConfig = {
providers: [
provideZonelessChangeDetection(),
]
};Une fois le provider zoneless configure, Zone.js n'est plus necessaire dans l'application. Le polyfill peut etre supprime du fichier angular.json (cibles build et test), puis le package desinstalle :
# Remove zone.js polyfill from angular.json build and test targets
# Then uninstall the package
npm uninstall zone.jsLa suppression de Zone.js elimine immediatement 33 Ko du bundle brut (environ 10 Ko apres compression gzip). Plus significatif encore, le navigateur n'execute plus le code de monkey-patching au demarrage, ce qui accelere le Time to Interactive de 15 a 25 % selon la complexite de l'application.
Declencheurs de la detection de changements en mode zoneless
Sans Zone.js, Angular ne surveille plus les operations asynchrones de maniere globale. La detection de changements repose desormais sur des mecanismes explicites. Les signals constituent le principal declencheur : toute modification d'un signal notifie automatiquement Angular que les templates qui le consomment doivent etre reevalues.
import { Component, signal, computed } from '@angular/core';
@Component({
selector: 'app-counter',
template: `
<div class="counter">
<button (click)="decrement()">-</button>
<span>{{ count() }}</span>
<button (click)="increment()">+</button>
<p>Double: {{ doubled() }}</p>
</div>
`
})
export class CounterComponent {
// Signal updates automatically notify the template
count = signal(0);
doubled = computed(() => this.count() * 2);
increment() {
this.count.update(v => v + 1);
// No markForCheck() needed - signal handles notification
}
decrement() {
this.count.update(v => v - 1);
}
}Dans cet exemple, la modification du signal count declenche une reevaluation ciblee du template du composant CounterComponent. Le signal computed doubled est automatiquement recalcule lorsque count change. Aucun cycle global de detection de changements n'est execute : Angular met a jour uniquement les bindings affectes.
Outre les signals, plusieurs autres mecanismes declenchent la detection de changements en mode zoneless :
- Les evenements du template (bindings
(click),(input), etc.) - Les
AsyncPipequi recoivent une nouvelle valeur d'un Observable - L'appel explicite a
ChangeDetectorRef.markForCheck() - Les outputs de composants enfants
- Le
setIntervalinterne utilise par Angular pour les animations
La strategie ChangeDetectionStrategy.OnPush reste pertinente en mode zoneless. Elle restreint la detection de changements aux composants dont les inputs ont change ou qui ont ete explicitement marques. Combiner OnPush avec les signals produit le modele de reactivite le plus performant disponible dans Angular : les mises a jour du DOM sont limitees au strict minimum, sans aucun cycle superflu.
Pieges de migration : setTimeout et formulaires reactifs
La transition vers le mode zoneless expose des patterns de code qui fonctionnaient grace a Zone.js mais qui cessent de mettre a jour le DOM sans celui-ci. Le cas le plus frequent concerne les mutations de proprietes classiques a l'interieur de callbacks asynchrones.
@Component({
selector: 'app-user-status',
template: `<span>{{ statusMessage }}</span>`
})
export class UserStatusComponent {
statusMessage = 'Loading...';
ngOnInit() {
setTimeout(() => {
// Zone.js would trigger CD here - zoneless does NOT
this.statusMessage = 'Ready';
}, 2000);
}
}Avec Zone.js, le callback de setTimeout declenchait automatiquement un cycle de detection de changements, et le template se mettait a jour. En mode zoneless, la mutation de statusMessage passe completement inapercue par Angular. Le template continue d'afficher "Loading..." indefiniment.
La solution consiste a convertir la propriete en signal :
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-user-status',
template: `<span>{{ statusMessage() }}</span>`
})
export class UserStatusComponent {
statusMessage = signal('Loading...');
ngOnInit() {
setTimeout(() => {
// Signal update notifies Angular automatically
this.statusMessage.set('Ready');
}, 2000);
}
}Le second piege majeur concerne les formulaires reactifs. Les FormControl d'Angular emettent des modifications via des Observables, et non via des signals. En mode zoneless, les changements de valeur d'un FormControl ne declenchent pas la detection de changements a moins d'etre explicitement convertis en signals avec toSignal() :
import { Component, inject, signal } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { toSignal } from '@angular/core/rxjs-interop';
import { debounceTime, distinctUntilChanged } from 'rxjs';
@Component({
selector: 'app-search',
imports: [ReactiveFormsModule],
template: `
<input [formControl]="searchControl" placeholder="Search..." />
<p>Results for: {{ searchTerm() }}</p>
`
})
export class SearchComponent {
searchControl = new FormControl('');
// Convert observable to signal for automatic template updates
searchTerm = toSignal(
this.searchControl.valueChanges.pipe(
debounceTime(300),
distinctUntilChanged()
),
{ initialValue: '' }
);
}La fonction toSignal() du package @angular/core/rxjs-interop souscrit a l'Observable et met a jour un signal a chaque emission. Ce pattern represente la passerelle officielle entre RxJS et le systeme de signals d'Angular.
Prêt à réussir tes entretiens Angular ?
Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.
SSR sans Zone.js : PendingTasks et serialisation
Le rendu cote serveur (SSR) avec Angular Universal pose un defi specifique en mode zoneless. Sans Zone.js, Angular ne peut plus detecter automatiquement quand toutes les operations asynchrones sont terminees avant de serialiser le HTML. Le service PendingTasks comble cette lacune en offrant un mecanisme explicite pour signaler les taches en cours.
import { Component, inject, signal } from '@angular/core';
import { PendingTasks } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { firstValueFrom } from 'rxjs';
@Component({
selector: 'app-data-loader',
template: `
@if (data()) {
<div>{{ data()!.title }}</div>
} @else {
<div>Loading...</div>
}
`
})
export class DataLoaderComponent {
private http = inject(HttpClient);
private pendingTasks = inject(PendingTasks);
data = signal<{ title: string } | null>(null);
ngOnInit() {
// PendingTasks.run() prevents SSR serialization until complete
this.pendingTasks.run(async () => {
const result = await firstValueFrom(
this.http.get<{ title: string }>('/api/data')
);
this.data.set(result);
});
}
}La methode PendingTasks.run() enregistre une tache asynchrone aupres du framework. Angular attend la completion de toutes les taches enregistrees avant de serialiser le HTML cote serveur. Sans ce mecanisme, le SSR produirait un HTML contenant l'etat "Loading..." au lieu des donnees resoluees, ce qui degraderait a la fois le SEO et l'experience utilisateur au premier rendu.
Le HttpClient enregistre automatiquement ses requetes aupres de PendingTasks en contexte SSR. En revanche, les operations asynchrones manuelles (fetch, setTimeout, API tierces) doivent etre explicitement enveloppees dans PendingTasks.run().
Benchmarks de performance
Les mesures suivantes, realisees sur une application de taille moyenne comportant environ 200 composants et 50 routes, illustrent les gains concrets apportes par le mode zoneless :
| Metrique | Zone.js | Zoneless | Amelioration | |--------|---------|----------|-------------| | Taille du bundle initial | +33 Ko brut / +10 Ko gzip | 0 Ko supplementaire | Reduction de 100 % | | Cycles de detection (interaction type) | 150-300 par interaction | 5-15 par interaction | 80-95 % en moins | | Profondeur des stack traces | 8-12 frames Zone supplementaires | Traces natives propres | Clarte immediate | | Time to Interactive (TTI) | Reference | 15-25 % plus rapide | Bootstrap Zone elimine |
La reduction du nombre de cycles de detection constitue le gain le plus significatif. En mode zoneless avec des signals, seuls les composants dont l'etat a effectivement change sont reevalues, contre un parcours complet de l'arbre de composants avec Zone.js. Cette precision produit des interfaces plus fluides, particulierement sur les appareils mobiles ou les ressources CPU sont limitees.
Certaines bibliotheques de l'ecosysteme Angular s'appuient encore sur Zone.js pour declencher la detection de changements. Avant de migrer vers le mode zoneless, il convient de verifier la compatibilite des dependances tierces. Angular Material et les composants CDK sont compatibles zoneless depuis Angular 19. NgRx fonctionne nativement avec les signals depuis la version 18. En revanche, certaines bibliotheques de graphiques ou de composants UI plus anciennes peuvent necessiter des adaptations. La commande ng update signale les incompatibilites connues.
API NgZone qui survivent en mode zoneless
Meme en mode zoneless, le service NgZone reste disponible dans Angular. Son comportement change cependant de maniere significative : NgZone.run() et NgZone.runOutsideAngular() deviennent des no-ops (operations sans effet). Le code qui les utilise continue de compiler et de s'executer, mais les callbacks ne beneficient plus du mecanisme de detection automatique de Zone.js.
Cette compatibilite descendante facilite la migration progressive. Les appels existants a NgZone.run() ne provoquent pas d'erreur mais n'ont plus aucun effet. Les developpeurs peuvent les supprimer progressivement, en les remplacant par des mises a jour de signals ou des appels explicites a ChangeDetectorRef.markForCheck().
Le pattern runOutsideAngular(), frequemment utilise pour executer des operations lourdes sans polluer la detection de changements (animations Canvas, WebSocket haute frequence), devient redondant en mode zoneless. Puisque les operations asynchrones ne declenchent plus la detection par defaut, toute execution se deroule deja "en dehors d'Angular".
Chronologie d'Angular 19 a 21 : la feuille de route du mode zoneless
L'adoption du mode zoneless suit une trajectoire progressive sur trois versions majeures d'Angular :
Angular 19 (novembre 2025) : introduction de provideExperimentalZonelessChangeDetection(). Les equipes qui souhaitent adopter le mode zoneless peuvent le faire en acceptant le risque de changements d'API dans les versions suivantes. Les composants Angular Material et CDK sont rendus compatibles. Le CLI affiche des avertissements pour les patterns de code incompatibles avec le mode zoneless.
Angular 20 (mai 2026) : stabilisation de l'API sous le nom provideZonelessChangeDetection(). Le prefixe Experimental disparait. Les outils de migration du CLI incluent des schematics pour convertir automatiquement les proprietes de composants en signals et envelopper les operations asynchrones dans PendingTasks.run(). La documentation officielle recommande le mode zoneless pour les nouveaux projets.
Angular 21 (novembre 2026) : le mode zoneless devient le comportement par defaut pour les projets generes via ng new. Zone.js reste disponible en option explicite (provideZoneChangeDetection()) pour les applications heritees qui ne peuvent pas migrer immediatement. Le bundle par defaut d'une application Angular neuve ne contient plus Zone.js.
Cette progression sur trois versions permet aux equipes de migrer a leur rythme. Les applications existantes ne sont jamais forcees de migrer : Zone.js restera supporte en tant qu'option explicite pour les cycles LTS.
Pour approfondir les concepts de detection de changements dans un contexte d'entretien technique, les ressources suivantes sur SharpSkill couvrent les questions les plus frequemment posees :
- questions d'entretien Angular -- couvre les mecanismes de detection de changements, les strategies Default et OnPush, et les scenarios de migration vers le mode zoneless
- top 25 des questions d'entretien Angular -- selection des questions les plus evaluees lors des entretiens techniques Angular en 2026
- module Angular Signals -- exercices pratiques sur les signals, les computed signals et l'integration avec les formulaires reactifs
Conclusion
Le mode zoneless d'Angular 19 et 20 represente une evolution fondamentale dans la gestion de la reactivite du framework. Les points essentiels a retenir :
- Zone.js intercepte plus de 130 API asynchrones du navigateur, generant des cycles de detection superflus et un surpoids de 33 Ko dans le bundle
- Angular 19 propose le mode zoneless en experimental via
provideExperimentalZonelessChangeDetection(), stabilise dans Angular 20 sousprovideZonelessChangeDetection() - Les signals constituent le mecanisme principal de notification en mode zoneless : toute modification d'un signal declenche une mise a jour ciblee du template
- Les proprietes classiques mutees dans des callbacks asynchrones (
setTimeout,setInterval) cessent de mettre a jour le DOM sans Zone.js -- la conversion en signals est indispensable - Les formulaires reactifs necessitent l'utilisation de
toSignal()pour convertir les Observables devalueChangesen signals compatibles avec le mode zoneless - Le service
PendingTasksgarantit la serialisation correcte du HTML en SSR en signalant les operations asynchrones en cours - Les benchmarks montrent une reduction de 80 a 95 % des cycles de detection de changements et un TTI ameliore de 15 a 25 %
- Angular 21, prevu pour fin 2026, active le mode zoneless par defaut dans les nouveaux projets, tout en conservant Zone.js comme option explicite pour les applications existantes
Passe à la pratique !
Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.
Tags
Partager
Articles similaires

Angular 19 en entretien : Signals, SSR et les questions incontournables
Les questions d'entretien Angular 19 les plus fréquentes : Signals, SSR avec hydratation incrémentale, détection de changement zoneless et nouvelles API réactives.

Angular Standalone Components : Migration et Bonnes Pratiques en 2026
Guide complet pour migrer les applications Angular des NgModules vers les standalone components. Processus de migration CLI en 3 etapes, lazy loading, routage, tests et bonnes pratiques pour Angular 21.

Angular 18 : Signals et nouvelles fonctionnalités
Découvrez les Signals d'Angular 18, la détection de changement zoneless et les nouvelles APIs signal-based pour créer des applications plus performantes.