Zone.js es un mecanismo de señalización que Angular usa para detectar cuándo el estado de una aplicación podría haber cambiado. Captura operaciones asíncronas como setTimeout, peticiones de red y escuchadores de eventos. Angular programa el change detection basándose en señales de Zone.js.
En algunos casos, las tareas o microtareas programadas no hacen ningún cambio en el modelo de datos, lo que hace que ejecutar change detection sea innecesario. Ejemplos comunes son:
requestAnimationFrame,setTimeoutosetInterval- Programación de tareas o microtareas por bibliotecas de terceros
Esta sección cubre cómo identificar tales condiciones, y cómo ejecutar código fuera de la zone de Angular para evitar llamadas innecesarias de change detection.
Identificando llamadas innecesarias de change detection
Puedes detectar llamadas innecesarias de change detection usando Angular DevTools. A menudo aparecen como barras consecutivas en la línea de tiempo del perfilador con origen setTimeout, setInterval, requestAnimationFrame, o un manejador de eventos. Cuando tienes llamadas limitadas dentro de tu aplicación de estas APIs, la invocación de change detection usualmente es causada por una biblioteca de terceros.
En la imagen anterior, hay una serie de llamadas de change detection disparadas por manejadores de eventos asociados con un elemento. Ese es un desafío común al usar componentes de terceros no nativos de Angular, que no alteran el comportamiento predeterminado de NgZone.
Ejecutar tareas fuera de NgZone
En tales casos, puedes indicarle a Angular que evite llamar change detection para tareas programadas por un fragmento de código dado usando NgZone.
Ejecutar fuera de la Zone
1import { Component, NgZone, OnInit } from '@angular/core';23@Component(...)4class AppComponent implements OnInit {5 private ngZone = inject(NgZone);67 ngOnInit() {8 this.ngZone.runOutsideAngular(() => setInterval(pollForUpdates), 500);9 }10}
El fragmento anterior le indica a Angular que llame setInterval fuera de la Zone de Angular y omita ejecutar change detection después de que pollForUpdates se ejecute.
Las bibliotecas de terceros comúnmente disparan ciclos innecesarios de change detection cuando sus APIs son invocadas dentro de la zone de Angular. Este fenómeno afecta particularmente a bibliotecas que configuran escuchadores de eventos o inician otras tareas (como temporizadores, peticiones XHR, etc.). Evita estos ciclos adicionales llamando a las APIs de la biblioteca fuera de la zone de Angular:
Mover la inicialización del gráfico fuera de la Zone
1import { Component, NgZone, OnInit } from '@angular/core';2import * as Plotly from 'plotly.js-dist-min';34@Component(...)5class AppComponent implements OnInit {6 private ngZone = inject(NgZone);78 ngOnInit() {9 this.ngZone.runOutsideAngular(() => {10 Plotly.newPlot('chart', data);11 });12 }13}
Ejecutar Plotly.newPlot('chart', data); dentro de runOutsideAngular le indica al framework que no debe ejecutar change detection después de la ejecución de tareas programadas por la lógica de inicialización.
Por ejemplo, si Plotly.newPlot('chart', data) agrega escuchadores de eventos a un elemento DOM, Angular no ejecuta change detection después de la ejecución de sus manejadores.
Pero a veces, puedes necesitar escuchar eventos despachados por APIs de terceros. En tales casos, es importante recordar que esos escuchadores de eventos también se ejecutarán fuera de la zone de Angular si la lógica de inicialización se realizó allí:
Verificar si el manejador es llamado fuera de la Zone
1import { Component, NgZone, OnInit, output } from '@angular/core';2import * as Plotly from 'plotly.js-dist-min';34@Component(...)5class AppComponent implements OnInit {6 private ngZone = inject(NgZone);78 plotlyClick = output<Plotly.PlotMouseEvent>();910 ngOnInit() {11 this.ngZone.runOutsideAngular(() => {12 this.createPlotly();13 });14 }1516 private async createPlotly() {17 const plotly = await Plotly.newPlot('chart', data);1819 plotly.on('plotly_click', (event: Plotly.PlotMouseEvent) => {20 // Este manejador será llamado fuera de la zone de Angular porque21 // la lógica de inicialización también es llamada fuera de la zone. Para verificar22 // si estamos en la zone de Angular, podemos llamar lo siguiente:23 console.log(NgZone.isInAngularZone());24 this.plotlyClick.emit(event);25 });26 }27}
Si necesitas despachar eventos a componentes padres y ejecutar lógica específica de actualización de vista, debes considerar volver a entrar a la zone de Angular para indicarle al framework que ejecute change detection o ejecutar change detection manualmente:
Volver a entrar a la zone de Angular al despachar evento
1import { Component, NgZone, OnInit, output } from '@angular/core';2import * as Plotly from 'plotly.js-dist-min';34@Component(...)5class AppComponent implements OnInit {6 private ngZone = inject(NgZone);78 plotlyClick = output<Plotly.PlotMouseEvent>();910 ngOnInit() {11 this.ngZone.runOutsideAngular(() => {12 this.createPlotly();13 });14 }1516 private async createPlotly() {17 const plotly = await Plotly.newPlot('chart', data);1819 plotly.on('plotly_click', (event: Plotly.PlotMouseEvent) => {20 this.ngZone.run(() => {21 this.plotlyClick.emit(event);22 });23 });24 }25}
El escenario de despachar eventos fuera de la zone de Angular también puede surgir. Es importante recordar que disparar change detection (por ejemplo, manualmente) puede resultar en la creación/actualización de vistas fuera de la zone de Angular.