Angular: Hooks del ciclo de vida de un componente

Angular: Hooks del ciclo de vida de un componente

Introducci贸n a los hooks de angular

Antes de empezar a describir los Hooks del ciclo de vida de un componente en angular quiero citar la definici贸n de Hooking de wikipedia:

El t茅rmino Hooking abarca una gama de t茅cnicas utilizadas para alterar o aumentar el comportamiento de un sistema operativo, aplicaciones o de otros componentes de software interceptando llamadas de funci贸n o mensajes o eventos pasados entre componentes de software. El c贸digo que maneja tales llamadas de funci贸n, eventos o mensajes interceptados se le llama un Hook.

hook
hook

En angular tenemos estos hooks, que son funciones ejecutadas en puntos claves del ciclo de vida de un componente.

Tanto los componentes y las directivas pueden ejecutar estas funciones. Pero las directivas no ejecutan las funciones de color azul, las cuales son espec铆ficas de la vista y contenido de componentes.

Dado que normalmente se utiliza typescript en Angular para obtener el c贸digo javascript final. Vamos a implementar estos m茅todos utilizando la interfaz que le corresponde a cada uno. Todas estas interfaces est谩n en el modulo @angular/core.

angular
angular

Por ejemplo, para implementar el m茅todo ngOnInit, debemos importar la interfaz OnInit:

import { Component, OnInit } from '@angular/core';

El lenguaje de javascript no tiene interfaces, estas interfaces las agrega typescript para obtener un Javascript fuertemente tipeado y basado en clases. Aunque las ultimas versiones de javascript utilizan una sintaxis de clases casi id茅ntica, por detr谩s realmente son funciones y herencia prototipal.

Todos los hooks del ciclo de vida de un componente en acci贸n

M谩s abajo tenemos ejemplos de todos los hooks en acci贸n y est谩n basados en el c贸digo de la documentaci贸n oficial de Angular. Para ver mejor el c贸digo y el funcionamiento en tiempo real, siempre puedes presionar este bot贸n:

download
fig. Download

Existe un componente de tipo ParentComponent y otro ChildComponent. El ParentComponent se encarga de crear el ChildComponent, de esta manera ser谩 m谩s f谩cil poder ver en acci贸n los hooks. Tambi茅n se encarga de bindear la propedad name y actualizarla para poder ver el hook ngOnChanges.

Como te dar谩s cuenta viendo el archivo /app/all/child.component.ts, este componente implementa todas las interfaces de los hooks.

Si presionas el bot贸n Crear ChildComponent, el mensaje #1 es en el constructor del componente, el constructor normalmente se utiliza para definir valores iniciales simples, nunca pongas mucha l贸gica en el constructor. Adem谩s podemos notar que la propiedad name en este punto, aun no esta definida.

Despu茅s tenemos el mensaje #2, este es del hook ngOnChanges, aqu铆 es cuando ocurre el enlazado de las propiedades con la nueva instancia de nuestro ChildComponent, en este punto la propiedad name ahora si esta definida.

Luego tenemos la ejecuci贸n de ngOnInit, ngDoCheck, ngAfterContentInit, ngAfterContentChecked, ngAfterViewInit, ngAfterViewChecked en ese orden.

Ahora si presionas el bot贸n Actualizar ChildComponent, se actualiza la propiedad name desde el ParentComponent, podemos ver como se ejecutan ngDoCheck, ngAfterContentChecked, ngAfterViewChecked, y otra vez ngOnChanges, ngDoCheck, ngAfterContentChecked y finalmente ngAfterViewChecked. Aqu铆 podemos afirmar dos cosas:

  1. ngDoCheck, ngAfterContentChecked, ngAfterViewChecked se invocan en ese orden al detectar un cambio.
  2. Tambi茅n se ejecutan siempre tras un ngOnChanges pues se volvieron a ejecutar en ese mismo orden

Finalmente, si presionas el bot贸n Destruir ChildComponent, los hooks ngDoCheck, ngAfterContentChecked, ngAfterViewChecked se vuelven a ejecutar, y adem谩s antes de destruir el componente totalmente, se ejecuta el hook ngOnDestroy.

Hooks ngOnChanges

Cuando se crea un componente lo primero que se ejecuta es el constructor de la clase del componente. Ahora bien, esto es totalmente normal en otros 谩mbitos. La creaci贸n de una nueva instancia de clase, as铆 que realmente constructor no es un hook de angular.

ngOnChanges solo revisa los cambios de una propiedad input, esto es, todas las propiedades a las que se les aplica el decorador @input([alias]), que es la manera en que bindeamos o enlazamos propiedades de un componente padre a otro hijo usando esta sintaxis [habilidad]="expresion". Este patr贸n, de padre a hijo, nos permite obtener un mejor rendimiento en la detecci贸n de cambios entre componentes. Es por eso que en el c贸digo de los ejemplos siempre se utiliza un componente padre y un componente hijo. Los cambios se actualizan de padre a hijo.

En el ejemplo tenemos dos propiedades enlazadas [persona]="persona"y [habilidad]="habilidad", si cambiamos el valor de habilidad se dispara la ejecuci贸n de ngOnChanges en el componente hijo, sin embargo la propiedad persona.name no dispara el hook, esto pasa porque la detecci贸n de cambios comprueba la referencia de la propiedad persona y lo que se modifico fue la propiedad persona.name, por lo que la referencia al objeto persona nunca cambi贸.

Cada vez que se detecta un cambio, ngOnChanges recibe un objeto, el cual contiene todas las propiedades que cambiaron, y cada una de estas es de tipo SimpleChange conteniendo el valor anterior previousValue y el valor actual currentValue.

ngOnChanges puede detectar cambios en m谩s de una propiedad @Input(), por lo que es muy 煤til para realizar m煤ltiples operaciones en la informaci贸n, como definir valores predeterminados, validar datos, transformarlos antes de ser utilizados por el componente y/o antes de ser visualizados por el usuario.

Hooks ngOnInit y ngOnDestroy

Hook ngOnInit

El hook ngOnInit se ejecuta una sola vez despu茅s de ngOnChanges y no se vuelve a ejecutar en el resto de vida del componente.

Una directiva puede tener hooks, esto se debe a que un componente es un tipo de directiva que tiene una plantilla html. Es decir, un componente es una directiva.

Dado que ngOnInit se ejecuta una sola vez despu茅s de ngOnChanges, es un buen lugar para:

  1. Agregar inicializaciones complejas.
    1. Como alg煤n proceso as铆ncrono
    2. Obtener de un servicio datos para uso en el componente
    3. Subscribirse a un objeto para ser notificado de cambios, como los observables de RxJs, escuchar eventos del DOM
    4. Etc茅tera.
  2. Configuraci贸n de inicio debido a que ya se tienen los valores de las propiedades @inputs() gracias al hook ngOnChanges.

Cada vez que se presiona el bot贸n Agregar Persona el hook ngOnInit se ejecuta, y cuando presionas el bot贸n Resetear se ejecuta el hook ngOnDestroy justo antes de destruir a las elementos personas.

Hook ngOnDestroy

En este hook del ciclo de vida de un componente deber铆a de ir todo el c贸digo de limpieza de un componente, justo antes de destruirlo.

Tambi茅n es un buen momento para notificar a otros componentes que va a ser destruido.

Es un buen punto donde liberar memoria, darse de baja de los Observadores como RxJS y los eventos del DOM. Detener los timers, dar de baja todos los callbacks enlazados a servicios. En general todo lo que consuma memoria y tiempo de ejecuci贸n.

Hook ngDoCheck

Este Hook del ciclo de vida de un componente se ejecuta despu茅s de cualquier ejecuci贸n de ngOnChange, y despues de ngOnInit, pero tambi茅n se ejecuta despu茅s de eventos que pueden producir cambios, como por ejemplo el blur y focus de los inputs.

Dado que se ejecuta demasiadas veces, es muy costoso utilizar este hook, as铆 que se debe usar con cuidado.

Para crear este ejemplo simplemente se implement贸 el m茅todo ngDoCheck en el componente hijo del c贸digo del hook ngOnChanges. Puedes probar cambiando datos y veras la ejecuciones de la funci贸n ngDoCheck.

ngOnChanges NO se ejecuta cuando modificamos una propiedad que no es un @input(), como pasa con la propiedad name del objeto persona, pero si se ejecuta el hook ngDoCheck. Puede ser 煤til para detectar cambios internos del componente, pero recuerda que tiene un costo alto de ejecuci贸n si deseas detectar muchas propiedades **NO @inputs**de tu componente.

Tambi茅n tenemos un elemento input dentro del componente hijo que esta enlazado a la propiedad [habilidad], esta es un @input, pero el cambio se realiza dentro del componente hijo, el componente padre no se entera de este cambio, por lo que aunque ngDoCheck se ejecuta, ngOnChanges no lo hace. Recuerda, la detecci贸n de cambios entre componentes se realiza de manera unidireccional, de padre a hijo.

Hooks AfterContentInit y AfterContentChecked

ngAfterContentInit se ejecuta una vez despu茅s del primer ngDoCheck y no se vuelve a ejecutar en todo el resto de vida del componente. ngAfterContentChecked se ejecuta despu茅s de cualquier ejecuci贸n de ngDoCheck en todo la vida del componente. Ambos se ejecutan despu茅s de que angular proyecta contenido externo en el componente.

Este contenido externo es proporcionado por el usuario del componente, similar a lo que se hace con el Light DOM de los componentes web nativos.

A lo anterior Angular le llama Proyecci贸n de contenido (Content projection), en AngularJS se conoce como transclusi贸n. Para proyectar el contenido en el componente, se debe utilizar el tag <ng-content></ng-content>, esta etiqueta ser铆a an谩loga a la etiqueta <slot> de los componenes web nativos.

Dentro del m茅todo ngAfterContentChecked se debe poder acceder al modelo o variables del contenido proyectado, para esto se debe utilizar el decorador @ContentChild([TipoDeHijo]), lo puedes ver en el archivo /app/after-content/after-content-child.component.ts.

// Para buscar contenido de tipo 'ChildComponent'(etiqueta lch-child) @ContentChild(ChildComponent) contentChild: ChildComponent;

Hooks AfterViewInit y AfterViewChecked

Estos hooks son muy similares a ngAfterContentInit y ngAfterContentChecked, solo que se invocan despu茅s de ngAfterContentChecked, siguiendo el mismo patron, **ngAfterViewInit** se invoca una sola vez despu茅s del primer ngAfterContentChecked, y no se vuelve a invocar.

ngAfterViewChecked, se ejecuta despu茅s de cualquier invocaci贸n de ngAfterContentChecked, la diferencia entre ngAfterContentChecked es que estos hooks marcan el final de la composici贸n de la vista de un componente y se define dentro del componente mismo, no desde el exterior, como vimos con ngAfterContentChecked y ng-content, por esta raz贸n tiene similitud con el Shadow DOM de los componentes web nativos.

Si revisas el c贸digo del /app/after-view/after-view-child.component.ts, para acceder al modelo o variables de la vista interna (Vista de hijos o Child View), debemos utilizar el decorator @ViewChild([TipoDeHijo]):

// Para buscar view childs del tipo ChildComponent @ViewChild(ChildComponent) viewChild: ChildComponent;

Otra cosa distinta a ngAfterContentChecked es que en ngAfterViewChecked no podemos cambiar el valor de inmediato de la propiedad comment enlazada con la vista, si lo haces, te saldr谩 un error como el siguiente:

Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: ''. Current value: 'Es un nombre muy grande'.

El error se debe que est茅 ultimo hook se ejecuta al final del ciclo de vida, entonces debe volver a pasar por ngOnChanges o ngDocheck para volver a renderizar la vista actualizada. La soluci贸n es actualizar la propiedad comment en una futura iteraci贸n del loop de Javascript. El setTimeout de this.logger.thick_then recibe una funci贸n, la cual se agrega a la cola de callbacks de javascript para ser escogida en una futura iteraci贸n del loop y esto le da el tiempo suficiente a Angular para actualizar la vista correctamente.

https://pensemosweb-mx.web.app/javascript-event-loop-concurrencia-asincrono/

Loop de Javascript

Podr铆a interesarte

驴C贸mo crear un servidor web en 5 minutos con Node.js?

Para crear un servidor web en node.js, primero, 驴Qu茅 es node.js?, tomando la definici贸n del sitio oficial, es un entorno de ejecuci贸n para JavaScript construido con el motor de JavaScript V8 de Chrome. Node.js usa un modelo de operaciones E/S sin bloqueo y orientado a eventos, que lo hace liviano y eficiente. El ecosistema de paquetes de Node.js, npm, es el ecosistema m谩s grande de librer铆as de c贸digo abierto en el mundo.