Detección de cambios angulares y la estrategia OnPush
Publicado: 2022-03-11Comenzaste a usar Angular para todos tus proyectos favoritos. Sabe lo que Angular tiene para ofrecer y cómo puede aprovecharlo para crear aplicaciones web sorprendentes. Pero hay ciertas cosas sobre Angular, y conocerlas puede ayudarlo a usar Angular mejor para sus proyectos.
Dado que el flujo de datos es el centro de casi todo lo relacionado con Angular, la detección de cambios es algo que vale la pena conocer, ya que lo ayudará a rastrear errores mucho más fácilmente y le dará la oportunidad de optimizar aún más sus aplicaciones cuando trabaje con un conjunto de datos complejo.
En este artículo, aprenderá cómo Angular detecta cambios en sus estructuras de datos y cómo puede hacerlas inmutables para aprovechar al máximo las estrategias de detección de cambios de Angular.
Detección de cambios en Angular
Cuando cambia cualquiera de sus modelos, Angular detecta los cambios e inmediatamente actualiza las vistas. Esta es la detección de cambios en Angular. El propósito de este mecanismo es asegurarse de que las vistas subyacentes estén siempre sincronizadas con sus modelos correspondientes. Esta característica central de Angular es lo que hace que el marco funcione y es en parte la razón por la cual Angular es una excelente opción para desarrollar aplicaciones web modernas.
Un modelo en Angular puede cambiar como resultado de cualquiera de los siguientes escenarios:
Eventos DOM (clic, pasar el cursor por encima, etc.)
Solicitudes AJAX
Temporizadores (setTimer(), setInterval())
Cambiar detectores
Todas las aplicaciones angulares se componen de un árbol jerárquico de componentes. En el tiempo de ejecución, Angular crea una clase de detector de cambios separada para cada componente del árbol, que eventualmente forma una jerarquía de detectores de cambios similar al árbol de jerarquía de componentes.
Cada vez que se activa la detección de cambios, Angular recorre este árbol de detectores de cambios para determinar si alguno de ellos ha informado cambios.
El ciclo de detección de cambios siempre se realiza una vez por cada cambio detectado y comienza desde el detector de cambios raíz y continúa hacia abajo de forma secuencial. Esta opción de diseño secuencial es buena porque actualiza el modelo de manera predecible, ya que sabemos que los datos de los componentes solo pueden provenir de su elemento principal.
Los detectores de cambios proporcionan una forma de realizar un seguimiento de los estados anteriores y actuales del componente, así como de su estructura, para informar cambios a Angular.
Si Angular obtiene el informe de un detector de cambios, indica al componente correspondiente que vuelva a renderizar y actualice el DOM en consecuencia.
Estrategias de detección de cambios
Valor frente a tipos de referencia
Para comprender qué es una estrategia de detección de cambios y por qué funciona, primero debemos comprender la diferencia entre los tipos de valor y los tipos de referencia en JavaScript. Si ya está familiarizado con su funcionamiento, puede omitir esta sección.
Para comenzar, revisemos los tipos de valor y los tipos de referencia y sus clasificaciones.
Tipos de valor
booleano
Nulo
Indefinido
Número
Cuerda
Para simplificar, uno puede imaginar que estos tipos simplemente almacenan su valor en la memoria de la pila (lo cual técnicamente no es cierto pero es suficiente para este artículo). Vea la memoria de pila y sus valores en la imagen a continuación, por ejemplo.
Tipos de referencia
arreglos
Objetos
Funciones
Estos tipos son un poco más complicados ya que almacenan una referencia en la memoria de la pila, que apunta a su valor real en la memoria del montón. Puede ver cómo la memoria de pila y la memoria de montón funcionan juntas en la imagen de ejemplo a continuación. Vemos que la memoria de la pila hace referencia a los valores reales del tipo de referencia en la memoria del montón.
La distinción importante que se debe hacer entre los tipos de valor y los tipos de referencia es que, para leer el valor del tipo de valor, solo tenemos que consultar la memoria de la pila, pero para leer el valor de un tipo de referencia, primero debemos consulte la memoria de la pila para obtener la referencia y, en segundo lugar, use esa referencia para consultar la memoria del montón para localizar el valor del tipo de referencia.
Estrategia predeterminada
Como dijimos anteriormente, Angular monitorea los cambios en el modelo para asegurarse de que capte todos los cambios. Verificará cualquier diferencia entre el estado anterior y el estado actual del modelo de aplicación general.

La pregunta que hace Angular en la estrategia de detección de cambios predeterminada es: ¿Ha cambiado algún valor en el modelo? Pero para un tipo de referencia, podemos implementar estrategias para que podamos hacer una mejor pregunta. Aquí es donde entra en juego la estrategia de detección de cambios OnPush.
Estrategia OnPush
La idea principal detrás de la estrategia OnPush se manifiesta al darse cuenta de que si tratamos los tipos de referencia como objetos inmutables, podemos detectar si un valor ha cambiado mucho más rápido. Cuando un tipo de referencia es inmutable, esto significa que cada vez que se actualiza, la referencia en la memoria de la pila tendrá que cambiar. Ahora simplemente podemos verificar: ¿Ha cambiado la referencia (en la pila) del tipo de referencia? En caso afirmativo, solo entonces verifique todos los valores (en el montón). Vuelva a consultar los diagramas de montón de pila anteriores si esto es confuso.
La estrategia OnPush básicamente hace dos preguntas en lugar de una. ¿Ha cambiado la referencia del tipo de referencia? En caso afirmativo, ¿han cambiado los valores en la memoria del montón?
Por ejemplo, suponga que tenemos una matriz inmutable con 30 elementos y queremos saber si hay algún cambio. Sabemos que, para que haya actualizaciones en la matriz inmutable, la referencia (en la pila) debería haber cambiado. Esto significa que inicialmente podemos verificar si la referencia a la matriz es diferente, lo que potencialmente nos evitaría hacer 30 verificaciones más (en el montón) para determinar qué elemento es diferente. Esto se llama la estrategia OnPush.
Entonces, podría preguntarse, ¿qué significa tratar los tipos de referencia como inmutables? Significa que nunca establecemos la propiedad de un tipo de referencia, sino que reasignamos el valor todos juntos. Vea abajo:
Tratar objetos como mutables:
static mutable() { var before = {foo: "bar"}; var current = before; current.foo = "hello"; console.log(before === current); // => true }
Tratar objetos como inmutables:
static mutable() { var before = {foo: "bar"}; var current = before; current = {foo "hello"}; console.log(before === current); // => false }
Tenga en cuenta que, en los ejemplos anteriores, estamos "tratando" los tipos de referencia como inmutables por convención, por lo que al final todavía estamos trabajando con objetos mutables, pero solo "fingimos" que son inmutables.
Entonces, ¿cómo implementa la estrategia OnPush para un componente? Todo lo que necesita hacer es agregar el parámetro changeDetection
en su anotación @Component.
import {ChangeDetectionStrategy, Component} from '@angular/core'; @Component({ // ... changeDetection: ChangeDetectionStrategy.OnPush }) export class OnPushComponent { // ... }
Inmutable.js
Es una buena idea hacer cumplir la inmutabilidad si uno decide usar la estrategia OnPush en un componente Angular. Ahí es donde entra Immutable.js.
Immutable.js es una biblioteca creada por Facebook para la inmutabilidad en JavaScript. Tienen muchas estructuras de datos inmutables, como List, Map y Stack. A los efectos de este artículo, se ilustrarán Lista y Mapa. Para obtener más información, consulte la documentación oficial aquí.
Para agregar Immutable.js a sus proyectos, asegúrese de ingresar a su terminal y ejecutar:
$ npm install immutable --save
También asegúrese de importar las estructuras de datos que está utilizando desde Immutable.js en el componente donde lo está utilizando.
import {Map, List} from 'immutable';
Así es como se puede usar un mapa Immutable.js:
var foobar = {foo: "bar"}; var immutableFoobar = Map(foobar); console.log(immutableFooter.get("foo")); // => bar
Y, se puede usar una matriz:
var helloWorld = ["Hello", "World!"]; var immutableHelloWorld = List(helloWorld); console.log(immutableHelloWorld.first()); // => Hello console.log(immutableHelloWorld.last()); // => World! helloWorld.push("Hello Mars!"); console.log(immutableHelloWorld.last()); // => Hello Mars!
Inconvenientes de usar Immutable.js
Hay un par de inconvenientes discutibles principales de usar Immutable.js.
Como habrás notado, es un poco engorroso usar su API, y es posible que a un desarrollador de JavaScript tradicional no le guste esto. Un problema más grave tiene que ver con no poder implementar interfaces para su modelo de datos, ya que Immutable.js no admite interfaces.
Envolver
Es posible que se pregunte por qué la estrategia OnPush no es la estrategia predeterminada para Angular. Supongo que es porque Angular no quería obligar a los desarrolladores de JavaScript a trabajar con objetos inmutables. Pero eso no significa que tengas prohibido usarlo.
Si eso es algo que desea aprovechar en su próximo proyecto web, ahora sabe lo fácil que es Angular para cambiar a una estrategia de detección de cambios diferente.