La detección de cambios es el proceso a través del cual Angular verifica si el estado de su aplicación ha cambiado y si es necesario actualizar algún DOM.

Cada componente de Angular tiene su propio detector de cambios y puedes ver toda la aplicación Angular como un árbol de componentes.

Angular ejecuta su mecanismo de detección de cambios, de arriba a abajo en el árbol, para que los cambios en el modelo de datos se reflejen en la vista de una aplicación.

¿Qué causa el cambio?

Resulta que estas tres cosas tienen algo en común, son todas asíncronas.
Estos son los únicos casos en los que Angular está realmente interesado en actualizar la vista.

¿Quién notifica a Angular?

Déjame que te presente a nuestro protagonista: zone.js, que es básicamente un contexto de ejecución para operaciones asíncronas.

Angular tiene su propia implementación de zone.js para hacer eso, un servicio llamado NgZone. Este servicio crea una zona denominada “zona angular” y, cuando el código se ejecuta dentro de esta zona, el mecanismo de detección de cambios de Angular se activa automáticamente y la vista se actualiza en consecuencia.

¿Qué problema tenemos con esto?

Esta verificación de detección de cambios (la mayor parte de las veces innecesaria) genera problemas de rendimiento cuando se trabaja con proyectos grandes o se maneja una enorme cantidad de datos, dando como resultado una experiencia de usuario muy mala.

Aquí viene lo interesante, veamos cómo podemos reducir las llamadas de detección de cambios en su aplicación.

A nivel de funcionalidad

NgZone, como hemos visto antes, es un servicio proporcionado por Angular que ayuda a administrar y controlar la ejecución de tareas asincrónicas y la detección de cambios.
El uso más común de este servicio es optimizar el rendimiento al iniciar un trabajo que consta de una o más tareas asincrónicas que no requieren actualizaciones de UI o manejo de errores para ser manejado por Angular. Estas tareas se pueden iniciar mediante runOutsideAngular() y, si es necesario, se pueden volver ejecutar dentro de la zona Angular mediante run().

Cualquier tarea o micro-tarea futura programada desde esta función continuará ejecutándose dentro/fuera de la zona Angular.

   import { Component , NgZone } de  '@angular/core' ; 

   @Component ({ 
     selector : 'app-example' , 
     template : ` 
       <button (click)="onClick()">Ejecutar código dentro de NgZone</button> 
     ` , 
   }) 
   export  class  ExampleComponent { 
     constructor ( private ngZone: NgZone ) {} 

     onClick ( ) { 
       this . ngZone . run ( () => { 
         // Código ejecutado dentro de NgZone 
         // Se activa la detección de cambios de Angular
        }); 
     } 
   }

En el siguiente enlace podemos ver un ejemplo muy interesante de este servicio.

A nivel de componente

Cada componente tiene su propia detección de cambios:

Estructura piramidal CD
  1. La referencia de entrada cambió. Si no proporcionamos una referencia a un nuevo objeto, sino que mutamos uno existente, el detector de cambios OnPush no se activará.
changeName() {
   // this.user.name = 'Alexandra'; -> Mutar un objeto existente
   this.user = { // -> Proporcionar una referencia nueva
     name: 'Alexandra'
   };
 }
  1. Un evento del componente o uno de sus hijos.
  2. El pipe async. Cuando se produce un cambio de datos en el observable, el pipe async marca automáticamente el componente para la verificación. En caso de destrucción de componentes, el pipe async cancela automáticamente la suscripción del observable, evitando así las posibilidades de una posible pérdida de memoria.
  3. Activando manualmente la detección de cambios.
La misma estructura anterior, solo que una rama es on push y la otra es cd

La detección de cambios que se invoca “manualmente”

Hay una forma más agresiva para reducir las comprobaciones de un componente y su subárbol: separar el detector de cambios del componente.

  constructor(private cd: ChangeDetectorRef){
    this.cd.detach(); // Angular no ejecutará el detector de cambios para este componente
  }

ChangeDetectorRef es una clase base que proporciona funcionalidad de detección de cambios. El árbol de detección de cambios recopila todas las vistas que se van a comprobar en busca de cambios.

Esta clase te proporciona métodos para agregar y eliminar vistas del árbol, iniciar la detección de cambios y marcar explícitamente las vistas como “dirty”, lo que significa que han cambiado y deben volver a procesarse.

Métodos: detectChanges, markForCheck, reattach, deatach, checkNoChanges

Puedes encontrar más información sobre los métodos de la clase en la web de Angular.

Bueno, y... ¡qué mejor manera de entender esto que de manera práctica!

Gif de gato preparándose para empezar

Ejemplo práctico

La siguiente aplicación se va a componer de un reloj que actualiza la hora cada segundo y una tabla donde se va a mostrar una lista de usuarios obtenidos de un servicio. Así mismo, este componente está formado por el componente fila, que se encarga de mostrar los datos de cada usuario y de generar un id radom para cada uno de ellos.

AppComponent, compuesto por HourComponent y TableComponent, del que cuelga RowComponent

Puedes encontrar la aplicación en el siguiente enlace.

¿Qué observamos?

Al ejecutar la aplicación, podemos observar que la columna Random va a generar de manera ilimitada un número por cada fila. Pero realmente el código no está definido con esa finalidad.

Esto es debido a que el componente hour está asignando un valor nuevo a la variable time cada segundo, disparando la detección de cambios. Por defecto, todos los componentes tienen definido ChangeDetectionStrategy.Default y se actualizan la vistas de todos ellos ejecutando a su vez la función getRandom().

Para evitarlo, definimos la estrategia de detección de cambios del componente row.component.ts a ChangeDetectionStrategy.OnPush (línea 12).

Con ello, conseguimos que los datos de la tabla se modifiquen cuando se produce un cambio de datos en el observable, en este caso cada vez que se llama a la función getUsers().

Conclusión

Espero que este post te haya sido útil para comprender el funcionamiento de la magia de Angular y darte cuenta de que un diseño deficiente puede afectar negativamente el rendimiento a medida que tu aplicación crece.

Referencias

Cuéntanos qué te parece.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.

Suscríbete