How to trigger input change event in Angular?

Detección de Cambios en Angular: Domina el Rendimiento

hace 9 años

Valoración: 4.69 (2362 votos)

La detección de cambios es un pilar fundamental en el framework Angular. Comprender cómo funciona, cuándo se ejecuta y cómo gestionarla eficientemente es esencial para desarrollar aplicaciones estables y con un rendimiento óptimo. Este artículo te guiará a través de los conceptos clave, las estrategias de detección de cambios y los errores comunes que debes evitar para asegurar que tu aplicación Angular funcione de la mejor manera posible.

How to detect change in input in Angular?
Change detection in Angular is implemented using Zone. js. Zone. js is a library that essentially patches many lower level browser APIs (like event handlers) so that the default functionality of the event occurs, as well as some custom Angular functionality.
Índice de Contenido

¿Qué es la Detección de Cambios en Angular?

En términos sencillos, la detección de cambios es el proceso mediante el cual Angular verifica si ha habido alguna modificación en tu aplicación y, en caso afirmativo, actualiza el DOM (Modelo de Objetos del Documento) para reflejar esos cambios en la interfaz de usuario. Imagina que tienes una aplicación que muestra información dinámica, como el nombre de un usuario o una lista de tareas. La detección de cambios se encarga de que, cuando esos datos se actualicen en tu código, la vista en el navegador también se actualice automáticamente, mostrando la información más reciente al usuario.

Este proceso, aunque pueda parecer invisible, ocurre constantemente en segundo plano y es crucial para la reactividad de Angular. Sin la detección de cambios, las actualizaciones en tus datos no se reflejarían en la pantalla, y tu aplicación no sería interactiva ni útil para el usuario.

Implementación de la Detección de Cambios con Zone.js

Angular implementa la detección de cambios utilizando una biblioteca llamada Zone.js. Zone.js es una herramienta poderosa que esencialmente "parchea" muchas APIs de bajo nivel del navegador, como los manejadores de eventos. Esto significa que cuando ocurre un evento del navegador, como un clic del ratón, Zone.js intercepta ese evento y, además de ejecutar la funcionalidad predeterminada del evento, también dispara la detección de cambios en Angular.

Zone.js monitorea una serie de eventos clave en una aplicación Angular, incluyendo:

  • Eventos del navegador: Clics, movimientos del ratón, pulsaciones de teclas, eventos de formularios, etc.
  • Timers: Funciones como setTimeout y setInterval.
  • Peticiones HTTP AJAX: Cuando tu aplicación realiza una petición a un servidor backend para obtener o enviar datos.

Cuando cualquiera de estos eventos ocurre en tu aplicación Angular, Zone.js notifica a Angular, desencadenando un ciclo de detección de cambios. Esencialmente, Zone.js actúa como un vigilante que está constantemente atento a cualquier cosa que pueda haber cambiado en la aplicación y que requiera una actualización de la interfaz de usuario.

Flujo de la Detección de Cambios en Angular

El ciclo de detección de cambios en Angular sigue un patrón específico en el árbol de componentes de tu aplicación. Angular comienza la verificación de cambios desde la parte inferior del árbol de componentes, es decir, desde los componentes hijos más profundos, y se mueve hacia arriba hasta la raíz de la aplicación.

Este proceso bottom-up (de abajo hacia arriba) se centra en marcar los componentes como "cambiados". Cuando Angular detecta un componente que ha sufrido alguna modificación y necesita ser actualizado, lo marca internamente. Una vez que se ha recorrido todo el árbol de componentes marcando los componentes modificados, Angular realiza una revisión top-down (de arriba hacia abajo) para renderizar los cambios en el DOM.

En resumen, la detección y el marcado de componentes modificados ocurre de abajo hacia arriba, mientras que la renderización de los cambios en la vista se realiza de arriba hacia abajo. Este flujo asegura que las actualizaciones se propaguen correctamente a través de toda la aplicación.

Detección de Cambios por Defecto: Dirty Checking

La estrategia de detección de cambios predeterminada en Angular se conoce como dirty checking (verificación sucia). Cada componente en Angular tiene asociado un detector de cambios que se encarga de determinar si algo ha cambiado en ese componente desde el último ciclo de detección de cambios.

El detector de cambios examina la información del componente, incluyendo variables, propiedades y la plantilla HTML. Compara el valor actual de cada elemento con su valor anterior. Si detecta alguna diferencia, marca el componente como "modificado", indicando que necesita ser actualizado en el DOM.

Es importante destacar que Angular es lo suficientemente inteligente como para solo verificar los cambios en los valores que se utilizan en la plantilla de un componente específico. Si tienes una propiedad en un objeto que no se utiliza en la plantilla de un componente, ese componente no perderá tiempo verificando si el valor de esa propiedad ha cambiado. Esto optimiza el proceso de detección de cambios y evita verificaciones innecesarias.

El punto clave de la detección de cambios por defecto es que Angular verifica cada componente en cada ciclo de detección de cambios. Incluso si un componente no ha sufrido ninguna modificación, Angular lo revisará para asegurarse de que no haya cambios pendientes. Si bien esta estrategia es sencilla y funciona bien en aplicaciones pequeñas y medianas, en aplicaciones grandes y complejas donde el rendimiento es crucial, puede convertirse en un cuello de botella. Verificar cada componente en cada ciclo, incluso cuando la mayoría de ellos no han cambiado, puede consumir recursos y afectar la fluidez de la aplicación.

Estrategia de Detección de Cambios OnPush

Para optimizar el rendimiento en aplicaciones Angular más grandes, existe una estrategia de detección de cambios alternativa llamada OnPush. Cuando configuras un componente para usar la estrategia OnPush, le estás indicando a Angular que solo debe verificar los cambios en ese componente en situaciones muy específicas.

Las situaciones en las que Angular realizará la detección de cambios en un componente OnPush son las siguientes:

  • Cambio en la referencia de un Input: Si la referencia de un Input (propiedad de entrada) del componente cambia. Esto significa que se le asigna un nuevo objeto o array al Input, no simplemente una modificación dentro del objeto o array existente.
  • Emisión de un Evento desde el Componente o sus Hijos: Si el propio componente o alguno de sus componentes hijos emite un evento.
  • Marcado Explícito del Componente como Necesitado de Verificación: Si el desarrollador marca explícitamente el componente como necesitado de verificación de cambios utilizando el ChangeDetectorRef y su método markForCheck().
  • Uso del Pipe Async en la Vista o Emisión de un Observable: Si se utiliza el pipe async en la plantilla del componente y el Observable asociado emite un nuevo valor.

Si ninguna de estas condiciones se cumple, Angular omitirá la verificación de cambios en ese componente durante el ciclo de detección de cambios. Esto puede suponer una mejora significativa en el rendimiento, especialmente en componentes que no cambian con frecuencia o que solo dependen de Inputs para su actualización.

Veamos un ejemplo para entender mejor cuándo se ejecuta la detección de cambios con OnPush y cuándo no:

this.items = [ { id: 1, title: 'Primer item' } ] // Ejemplo 1: No se ejecutará la detección de cambios en un componente OnPush // El título en la pantalla seguirá siendo "Primer item" this.items[0].title = 'Primer item actualizado'; // Ejemplo 2: Sí se ejecutará la detección de cambios, ya que la referencia de this.items cambió const [first, ...rest] = this.items; first.title = 'Primer item actualizado'; this.items = [first, ...rest]; 

En el Ejemplo 1, aunque se modifica la propiedad title del primer elemento del array items, la referencia al array en sí no cambia. Con la estrategia OnPush, Angular no detectará este cambio porque la referencia de this.items sigue siendo la misma. Por lo tanto, la vista no se actualizará.

En el Ejemplo 2, se crea un nuevo array y se asigna a this.items. En este caso, la referencia de this.items sí cambia. Como resultado, la detección de cambios se ejecutará en el componente OnPush y la vista se actualizará para mostrar el título modificado.

Además, como se mencionó anteriormente, la detección de cambios también se activará si se hace clic en un botón dentro del componente OnPush o si se emite algún otro evento, ya sea directamente en el componente o en uno de sus componentes hijos.

Posibles Errores Comunes y Problemas de Rendimiento

Existen ciertas prácticas en el desarrollo de aplicaciones Angular que pueden afectar negativamente al rendimiento debido a la detección de cambios. Evitar estos errores te ayudará a construir aplicaciones más eficientes. Veamos algunos ejemplos:

Llamar a Métodos de Clase desde la Plantilla

Un error común, incluso para desarrolladores experimentados, es llamar a métodos de la clase del componente directamente desde la plantilla HTML para mostrar datos o modificar la visualización. Considera este ejemplo:

export class MiComponente { public usuario$!: Observable = of({ nombre: 'Juan', apellido: 'Pérez', }); getNombreCompleto(usuario: Usuario) { return usuario.nombre + ' ' + usuario.apellido; } } 

{{ getNombreCompleto(usuario) }}

En este ejemplo, tenemos un método getNombreCompleto que concatena el nombre y el apellido de un usuario. Aunque parece una forma sencilla de mostrar el nombre completo en la plantilla, este enfoque puede generar problemas de rendimiento.

El problema radica en que Angular no puede saber si el valor de retorno de getNombreCompleto ha cambiado o no en cada ciclo de detección de cambios. Como no puede estar seguro, Angular se ve obligado a ejecutar el método getNombreCompleto en cada ciclo de detección de cambios para asegurarse de que el valor mostrado en la vista sea correcto. Si la detección de cambios se ejecuta 100 veces en tu aplicación, el método getNombreCompleto se llamará 100 veces, incluso si las propiedades nombre y apellido nunca cambian.

En este ejemplo concreto, el impacto en el rendimiento puede ser mínimo, ya que solo se están concatenando dos cadenas de texto. Sin embargo, si el método que se llama desde la plantilla es más complejo y costoso en términos de procesamiento, el efecto negativo en el rendimiento puede ser considerable. Imagina un método que realiza cálculos intensivos, consultas a una base de datos simulada o cualquier otra operación que consuma tiempo de CPU. Llamar a ese método en cada ciclo de detección de cambios puede ralentizar significativamente tu aplicación.

Solución: Calcular los Valores en el Componente

La solución a este problema es evitar llamar a métodos complejos directamente desde la plantilla. En lugar de eso, calcula los valores que necesitas mostrar en la vista dentro de la clase del componente y expónlos como propiedades.

En el ejemplo anterior, podemos agregar una propiedad nombreCompleto al objeto Usuario y calcular su valor cuando obtenemos los datos del usuario:

interface UsuarioExtendido extends Usuario { nombreCompleto: string; } export class MiComponente { public usuario$!: Observable = of({ nombre: 'Juan', apellido: 'Pérez', }).pipe( map((usuario: Usuario) => { return { ...usuario, nombreCompleto: this.getNombreCompleto(usuario), }; }) ); getNombreCompleto(usuario: Usuario) { return usuario.nombre + ' ' + usuario.apellido; } } 

{{ usuario.nombreCompleto }}

Ahora, en lugar de llamar al método getNombreCompleto desde la plantilla, calculamos el valor de nombreCompleto en la tubería del Observable utilizando el operador map. El método getNombreCompleto solo se ejecuta cuando el Observable emite un nuevo valor. Hasta entonces, la detección de cambios omitirá este componente y no ejecutará el método innecesariamente en cada ciclo.

Pipes Impuros

Otra fuente potencial de problemas de rendimiento relacionados con la detección de cambios son los pipes impuros. Por defecto, los pipes en Angular son puros. Esto significa que Angular solo ejecuta un pipe puro cuando detecta un cambio puro en el valor de entrada. Un cambio puro ocurre cuando el valor de entrada es un tipo primitivo (String, Number, Boolean o Symbol) y cambia su valor, o cuando la referencia de un objeto (como un Array, Object, Date o Function) cambia.

Los pipes impuros, en cambio, se ejecutan en cada ciclo de detección de cambios, independientemente de si el valor proporcionado al pipe ha cambiado o no. Si bien puedes crear y utilizar pipes impuros, se desaconseja encarecidamente su uso. Pueden afectar drásticamente el rendimiento de tu aplicación, especialmente si realizan operaciones costosas.

Hooks de Ciclo de Vida Complejos y ngOnChanges

El uso de hooks de ciclo de vida complejos que se ejecutan en cada ciclo de detección de cambios, como ngOnChanges, también puede impactar negativamente en el rendimiento. Si necesitas utilizar alguno de estos hooks, asegúrate de que su alcance sea limitado y de que solo realicen el trabajo necesario.

En lugar de tener un ngOnChanges que realiza operaciones costosas en cada ciclo:

ngOnChanges(changes: SimpleChanges) { // Operación costosa que se ejecuta en cada ciclo de detección de cambios hacerAlgoCostoso(); } 

Considera limitar el alcance de la operación y ejecutarla solo cuando sea necesario:

ngOnChanges(changes: SimpleChanges) { if (changes.miInput && changes.miInput.currentValue === 'Condición Específica') { // Operación costosa solo cuando el Input 'miInput' cambia a 'Condición Específica' hacerAlgoCostoso(); } } 

Setters de TypeScript para Inputs

Una recomendación para gestionar los cambios en las propiedades de entrada (Inputs) de un componente de manera eficiente es utilizar setters de TypeScript en lugar de ngOnChanges. Un setter te permite ejecutar código personalizado cada vez que se asigna un nuevo valor a una propiedad.

_miInput: any; @Input() set miInput(valor: any) { this._miInput = valor; // Código que se ejecuta solo cuando 'miInput' cambia hacerAlgoSoloConCambioDeInput(valor); } get miInput(): any { return this._miInput; } 

Utilizar setters asegura que el código se ejecute solo cuando el Input específico cambia y no en cada ciclo de detección de cambios, lo que puede mejorar el rendimiento en comparación con ngOnChanges si solo te interesa reaccionar a cambios en Inputs específicos.

Preguntas Frecuentes sobre la Detección de Cambios en Angular

¿Puedo desactivar la detección de cambios en Angular?

No puedes desactivar por completo la detección de cambios en Angular, ya que es un mecanismo central del framework. Sin embargo, puedes optimizarla utilizando la estrategia OnPush para reducir la frecuencia de las verificaciones en componentes que no cambian con frecuencia.

¿Cuándo debo usar la estrategia OnPush?

Deberías considerar usar la estrategia OnPush en componentes que:

  • Son componentes "puros" que principalmente muestran datos basados en sus Inputs y no tienen mucha lógica interna que pueda cambiar independientemente de los Inputs.
  • Forman parte de aplicaciones grandes o complejas donde el rendimiento es crucial.
  • Se renderizan muchas veces o contienen gran cantidad de elementos en su plantilla.

¿Cómo puedo forzar la detección de cambios manualmente?

Puedes forzar la detección de cambios manualmente utilizando el ChangeDetectorRef inyectado en tu componente. Utiliza los siguientes métodos:

  • ChangeDetectorRef.detectChanges(): Realiza la detección de cambios para este componente y sus hijos.
  • ChangeDetectorRef.markForCheck(): Marca el componente y sus ancestros para que sean verificados en el próximo ciclo de detección de cambios. Es más eficiente que detectChanges() porque solo marca los componentes y no fuerza la detección inmediata.
  • ChangeDetectorRef.detach(): Desconecta el detector de cambios del componente del árbol de detección de cambios. El componente ya no se verificará automáticamente. Útil en casos muy específicos y avanzados.
  • ChangeDetectorRef.reattach(): Vuelve a conectar un detector de cambios desconectado al árbol de detección de cambios.

¿Qué impacto tiene Zone.js en el tamaño de mi bundle?

Zone.js es una biblioteca relativamente pequeña y su impacto en el tamaño del bundle de tu aplicación Angular suele ser mínimo en comparación con el tamaño total de la aplicación. Los beneficios que aporta en términos de simplificar la detección de cambios y la reactividad generalmente superan el pequeño aumento en el tamaño del bundle.

Conclusión

La detección de cambios es un concepto fundamental en Angular que impulsa la reactividad y la actualización de la interfaz de usuario. Comprender cómo funciona, las diferentes estrategias disponibles y los posibles errores comunes es esencial para construir aplicaciones Angular de alto rendimiento y con una experiencia de usuario fluida.

Al dominar la detección de cambios, podrás optimizar tus aplicaciones, evitar cuellos de botella de rendimiento y crear experiencias web más rápidas y eficientes. Experimenta con las estrategias de detección de cambios, analiza el rendimiento de tus componentes y aplica las mejores prácticas para asegurar que tu aplicación Angular funcione de la mejor manera posible. Recuerda que la optimización del rendimiento es un proceso continuo, y la detección de cambios es un área clave para enfocar tus esfuerzos.

Subir