Zone.js est un mécanisme de signalement qu'Angular utilise pour détecter quand l'état d'une application a pu changer. Il capture les opérations asynchrones comme setTimeout, les requêtes réseau et les event listeners. Angular planifie la détection de changements en fonction des notifications de Zone.js.
Dans certains cas, les tâches ou microtâches planifiées ne modifient pas le modèle de données, ce qui rend l'exécution de la détection de changements inutile. Exemples courants :
requestAnimationFrame,setTimeoutousetInterval- La planification de tâches ou de microtâches par des libraries tierces
Cette section explique comment identifier ces situations et comment exécuter du code en dehors de la zone Angular pour éviter des appels inutiles à la détection de changements.
Identifier les appels inutiles à la détection de changements
Vous pouvez détecter les appels inutiles à la détection de changements avec Angular DevTools. Ils apparaissent souvent sous forme de barres consécutives dans la timeline du profiler, avec pour source setTimeout, setInterval, requestAnimationFrame ou un event handler. Lorsque votre application fait peu appel à ces API, le déclenchement de la détection de changements est généralement dû à une library tierce.

Dans l'image ci-dessus, on voit une série d'appels à la détection de changements déclenchés par des event handlers associés à un élément. C'est un problème courant avec les composants tiers, non natifs d'Angular, qui ne modifient pas le comportement par défaut de NgZone.
Exécuter des tâches en dehors de NgZone
Dans ces cas-là, vous pouvez indiquer à Angular de ne pas déclencher la détection de changements pour les tâches planifiées par un morceau de code donné, à l'aide de NgZone.
Run outside of the Zone
1import { Component, NgZone, OnInit, inject } from '@angular/core';
2
3@Component(...)
4class App implements OnInit {
5 private ngZone = inject(NgZone);
6
7 ngOnInit() {
8 this.ngZone.runOutsideAngular(() => setInterval(pollForUpdates, 500));
9 }
10}
L'extrait précédent indique à Angular d'appeler setInterval en dehors de la zone Angular et de ne pas exécuter la détection de changements après l'exécution de pollForUpdates.
Les libraries tierces déclenchent souvent des cycles de détection de changements inutiles lorsque leurs API sont invoquées dans la zone Angular. Ce phénomène touche particulièrement les libraries qui mettent en place des event listeners ou lancent d'autres tâches (comme des timers, des requêtes XHR, etc.). Évitez ces cycles supplémentaires en appelant les API de la library en dehors de la zone Angular :
Move the plot initialization outside of the Zone
1import { Component, NgZone, OnInit, inject } from '@angular/core';
2import * as Plotly from 'plotly.js-dist-min';
3
4@Component(...)
5class App implements OnInit {
6 private ngZone = inject(NgZone);
7
8 ngOnInit() {
9 this.ngZone.runOutsideAngular(() => {
10 Plotly.newPlot('chart', data);
11 });
12 }
13}
Exécuter Plotly.newPlot('chart', data); dans runOutsideAngular indique au framework qu'il ne doit pas exécuter la détection de changements après l'exécution des tâches planifiées par la logique d'initialisation.
Par exemple, si Plotly.newPlot('chart', data) ajoute des event listeners à un élément du DOM, Angular n'exécute pas la détection de changements après l'exécution de leurs handlers.
Mais il arrive que vous deviez écouter des événements émis par des API tierces. Dans ce cas, n'oubliez pas que ces event listeners s'exécuteront eux aussi en dehors de la zone Angular si la logique d'initialisation y a été exécutée :
Check whether the handler is called outside of the Zone
1import { Component, NgZone, OnInit, output, inject } from '@angular/core';
2import * as Plotly from 'plotly.js-dist-min';
3
4@Component(...)
5class App implements OnInit {
6 private ngZone = inject(NgZone);
7
8 plotlyClick = output<Plotly.PlotMouseEvent>();
9
10 ngOnInit() {
11 this.ngZone.runOutsideAngular(() => {
12 this.createPlotly();
13 });
14 }
15
16 private async createPlotly() {
17 const plotly = await Plotly.newPlot('chart', data);
18
19 plotly.on('plotly_click', (event: Plotly.PlotMouseEvent) => {
20 // This handler will be called outside of the Angular zone because
21 // the initialization logic is also called outside of the zone. To check
22 // whether we're in the Angular zone, we can call the following:
23 console.log(NgZone.isInAngularZone());
24 this.plotlyClick.emit(event);
25 });
26 }
27}
Si vous devez émettre des événements vers des composants parents et exécuter une logique précise de mise à jour de la vue, envisagez de revenir dans la zone Angular pour indiquer au framework d'exécuter la détection de changements, ou exécutez la détection de changements manuellement :
Re-enter the Angular zone when dispatching event
1import { Component, NgZone, OnInit, output, inject } from '@angular/core';
2import * as Plotly from 'plotly.js-dist-min';
3
4@Component(...)
5class App implements OnInit {
6 private ngZone = inject(NgZone);
7
8 plotlyClick = output<Plotly.PlotMouseEvent>();
9
10 ngOnInit() {
11 this.ngZone.runOutsideAngular(() => {
12 this.createPlotly();
13 });
14 }
15
16 private async createPlotly() {
17 const plotly = await Plotly.newPlot('chart', data);
18
19 plotly.on('plotly_click', (event: Plotly.PlotMouseEvent) => {
20 this.ngZone.run(() => {
21 this.plotlyClick.emit(event);
22 });
23 });
24 }
25}
Il peut aussi arriver que des événements soient émis en dehors de la zone Angular. N'oubliez pas que déclencher la détection de changements (par exemple manuellement) peut entraîner la création ou la mise à jour de vues en dehors de la zone Angular.