Retomamos esta saga sobre el uso avanzado de componentes y databindingen Angular. Aquí tienes las partes #1 y #2 si te las perdiste.
¿MÁS PERDID@ QUE UN PULPO EN UN GARAJE?
Si acabas de aterrizar aquí y estos temas de Angular te suenan a chino, te recomiendo que empieces por este artículo de introducción a Angular con las claves para entenderlo.
Qué es el ciclo de vida de un componente
Para entender el ciclo de vida de un componente (en inglés, component lifecycle) debemos explicar ese método que se añade automáticamente cada vez que creamos un componente: el ngOnInit. Ve a cualquier de tus componentes, al archivo TypeScript (en adelante, TS), y lo encontrarás. Por defecto tiene este aspecto:
ngOnInit() { }
NgOnInit es una de las etapas (en inglés, hooks) del lifecycle de un componente. Vamos a verlas todas en detalle.
Cuando creamos un componente, Angular instancia dicho componente y lo insertar en el DOM. Durante este proceso de creación del componente (en inglés, instantiation), Angular pasa por distintas fases.
Lo que Angular hace es diferenciar dichas fases para que nosotros podamos ejecutar código dentro de la fase que necesitemos.
Estas fases son los llamados lifecycle hooks de Angular
Yo me imagino estas fases como las distintas etapas de un tour, en las que Angular nos deja "entrar" y ejecutar código (nos ofrece métodos específicos) dentro de cada una de ellas.
Aquí un esquemita para entendernos mejor:
ngOnChanges: el primer hook en el que podemos entrar (el primero al que Angular llama después del constructor). Se puede ejecutar múltiples veces: una al principio, cuando un componente es creado, y cada vez que una de nuestras propiedades decoradas con @Input sufra algún cambio.
ngOnInit: este segundo hook se ejecuta una sola vez, cuando el componente es instanciado. Se ejecuta después del constructor.
ngDoCheck: se ejecutará múltiples veces, siempre que el sistema de change detection se active. Este sistema es lo que permite a Angular detectar si ha habido alguna actividad en la template de un componente. Ojo, porque "alguna actividad" no significa que se haya producido un cambio. Por ejemplo, podemos haber hecho click en un botón que no cambia nada. Aún así, Angular detecta esta actividad (porque igualmente es un evento) usando este hook. Así que, como comprenderás, este hook se ejecuta constantemente. Esto no supone un problema de eficiencia o performance para Angular, ya que el sistema de change detection funciona de una manera muy eficiente.
ngAfterContentInit: este hook es ejecutado cuando el contenido proyectado a través de <ng-content> se inicializa. Es decir, el contenido que va entre las etiquetas de apertura y cierre de tu HTML custom element.
ngAfterContentChecked: este hook se ejecuta cada vez que el contenido proyectado a través de <ng-content> tiene alguna actividad, aunque eso no haya supuesto ningún cambio. Es el mismo caso que en el hook ngDoCheck. Es decir, Angular usa su sistema de change detection.
ngAfterViewInit: esta fase se ejecuta después de que la template de un componente se haya inicializado.
ngAfterViewChecked: esta fase se ejecuta cada vez que Angular detecta una actividad (usando change detection) en la templatedel componente.
ngOnDestroy: se ejecuta al destruir un componente. Un caso típico de destrucción de un componente es cuando le aplicamos un ngIf y su valor resulta ser false. En este caso, el componente será eliminado del DOM. Este hook es llamado justo antes de la destrucción del componente. Por eso, es una etapa perfecta para "hacer limpieza".
Lifecycle hooks en acción
Basta de teoría, ¡vamos a la práctica! Vamos a utilizar el ServerElementComponent para practicar los hooks, que ya trae el constructor y el ngOnInit.
ngOnInit
Hacemos un console.log en el constructor para que podamos ver en qué momento es llamado el constructor. Lo mismo para el método ngOnInit. Este será el componente donde añadiremos todos los hooks, pero empecemos por estos dos de momento.
constructor() {
console.log('constructor was called!');
}
ngOnInit() {
console.log('ngOnInit was called!');
}
Si ahora guardas y vas a las dev tools del navegador, verás que ambos métodos han sido llamados, primero el constructor y luego el ngOnInit. No tiene nada que ver el orden en el que los hayamos escrito en el archivo TS, porque el orden de ejecución será siempre el mismo.
Prueba a escribir algo en los dos inputs y añadir un nuevo server o blueprint. Verás que el constructor y el ngOnInit se vuelven a ejecutar, ya que una nueva instancia (en inglés,instance)del componente ServerElement ha sido creada.
ngOnChanges
El segundo hook que vamos a probar es el ngOnChanges, que como ya hemos dicho, es el primer lifecycle hook que se ejecuta después del constructor. Como regla general, declararlo como método sería suficiente, pero es una buena práctica (aplicable a todos los hooks) implementar la interfaceen la clase, como de hecho Angular hace por defecto con la interface de ngOnInit.
Además también debemos importar el hook desde el módulo angular/core, y lo mismo es aplicable para todos los hooks.
1. Para probar el ngOnChanges hacemos lo mismo que antes: escribimos un console.log para que podamos ver en las dev tools cuándo se ejecuta.
Ojo, porque ngOnChanges es el único hook que recibe un argumento (changes: SimpleChanges), que también debe ser importado desde angular/core.
2. Hacemos un console.log del argumento changes para que ver qué nos devuelve la consola.
ngOnChanges(changes: SimpleChanges) {
console.log('ngOnChanges was called!');
console.log(changes);
}
Si guardas y vas a tus dev tools, verás que ngOnChanges ha sido llamado el primero (después del constructor) y que el parámetro changes que le habíamos pasado al método ngOnChanges nos devuelve un objeto que, ¡sorpresa ! contiene nuestra propiedad elementdeclarada en el archivo server-element.component.ts.
Esto ocurre porque es una propiedad decorada con el @Input decorator.
Podemos observar que Angular nos proporciona cierta información, como el contenido de dicha propiedad, si es el primer cambio que hacemos o no, o cuál era su valor previamente, que en este caso es ninguno (undefined), porque no ha habido ningún cambio desde que este componente ha sido instanciado.
3. Para poder ver cómo el ngOnChanges se dispara más de una vez, vamos a crearnos un botón en el template del AppComponent, justo antes del <app-server-element>. Le añadimos un click event vinculado a un método llamado onChangeFirst que posteriormente crearemos.
<button class="btn btn-primary" (click)="onChangeFirst()">Change first element</button>
El objetivo de esa función será cambiar el nombre del título del primer elemento (en la imagen de arriba, cambiaría el nombre del "Rocket Server", para entendernos).
Para que nuestro ejemplo funcione, tenemos que hacer unos cambios en el archivo server-element.component.ts. Verás que ahora estamos obteniendo el elemento completo:
Pero a partir de ahora sólo vamos a necesitar el name, porque de hecho esa es la única propiedad que estábamos reclamando en la template del ServerElementComponent (échale un vistazo).
4. Así que nos olvidamos de la propiedad element y creamos una nueva propiedad (name), que será de tipo string.
@Input() name: string;
5. Vamos a mostrar esa propiedad en la template del ServerElement mediante string interpolation. Nos deshacemos también del código donde mostrábamos el element.name.
Recuerda que el resto de elementos de la card (el card-body, vaya) se proyectan vía ngContent.
6. Ahora en el app.component.html, cuando iteramos el array serverElements, ya no necesitamos vincular la propiedad srvElement, sino la propiedad que acabamos de crear (name). La vinculamos mediante property binding al serverElement.name.
<app-server-element *ngFor="let serverElement of serverElements"
[srvElement]="serverElement"
[name]="serverElement.name">
De hecho, puedes eliminar la vinculación al alias srvElement, pero yo la dejo ahí como referencia.
7. Creamos el métodoonChangeFirst en el archivo app.component.ts. Vamos simplemente a cambiar el nombre del primer elemento del array serverElements por el que tú quieras.
Y ahora, si guardas y vas a tus dev tools, mira lo que pasa cuando haces click en "Change first element".
¡Exacto! El nombre cambia, ngOnChanges se dispara al hacer click en el botón y te devuelve un conjunto de información sobre el elemento que acabas de cambiar.
ngDoCheck
Igual que sus otros compañeros hooks, ngDoCheck también debe implementar su interface y ser importado desde angular/core. Recuerda que este hook es llamado cada vez que change detection se ejecuta cuando Angular detecta que ha habido alguna actividad.
1. Añadimos el método al archivo server-element.component.ts y hacemos un console-log para ver en las dev tools en qué momento Angular llama a este método.
ngDoCheck() {
console.log('ngDoCheck was called!');
}
Si vas a tus dev tools, verás que el hook ha sido llamado dos veces. La segunda es simplemente porque estamos trabajando en modo de desarrollo (en inglés, development mode), y en este modo Angular ejecuta una comprobación extra.
Verás que al hacer click en cualquiera de nuestros botones, el ngDoCheck se dispara.
ngDoCheck es un hook muy útil para el caso en el que quieras comprobar algo manualmente que por alguna razón Angular no detecta.
AfterContentInit
Como siempre, impórtalo desde angular/core e implementa su interface en la clase de TS.
Vamos a crear el método ngAfterContentInit y a escribir un console.log para ver en qué momento nos lo imprime la consola, como con los hooks anteriores.
ngAfterContentInit() {
console.log('ngAfterContentInit was called!');
}
Si guardas y vas a tus dev tools, verás que ngAfterContentInit es llamado una sola vez, después de ngDoCheck. Recuerda que sólo es llamado al inicializar el contenido (initialize ), que hemos proyectado a través de <ng-content>. Es decir, verás que sólo es llamado cuando creamos una de nuestras cards al hacer click en "Add server" o "Add server blueprint".
ngAfterContentChecked
Lo importamos y declaramos su interface como hemos hecho con el resto de hooks. Vamos a hacer nuevamente un console.log en el método para ver qué nos devuelven las dev tools.
ngAfterContentChecked() {
console.log('ngAfterContentChecked was called!');
}
Si guardas y observas tus dev tools, verás que sigue el mismo patrón que el ngDoCheck: es llamado después de cada ciclo de change detection.
ngAfterViewInit
Lo importamos y declaramos su interface como hemos hecho con el resto de hooks. Vamos a hacer nuevamente un console.log en el método para ver qué nos devuelven las dev tools. Verás que este hook ocurre después de que la view del componente se ha inicializado.
ngAfterViewInit() {
console.log('ngAfterViewInit was called!');
}
ngAfterViewChecked
Lo importamos y declaramos su interface como hemos hecho con el resto de hooks. Vamos a hacer nuevamente un console.log en el método para ver qué nos devuelven las dev tools. Este hook es llamado cada vez que Angular comprueba (mediante su sistema de change detection) si la view del componente ha tenido alguna actividad (que no necesariamente un cambio).
ngAfterViewChecked() {
console.log('ngAfterViewChecked was called!');
}
ngOnDestroy
El último de nuestros hooks es llamado justo antes de que nuestro componente sea destruído.
No olvidemos importarlo y declarar su interface como hemos hecho con el resto de hooks. Vamos a hacer nuevamente un console.log en el método para ver qué nos devuelven las dev tools.
ngOnDestroy() {
console.log('ngOnDestroy was called!');
}
1. Para poder ver este hook en acción necesitamos crear una manera de destruir nuestro componente, así que nos creamos un botón "destructor" en el archivo app.component.html, justo después del botón "Change first element".
2. Le añadimos un click event vinculado a un evento llamado onDestroyFirst, en referencia al componente que queremos destruir.
Y con esta lógica, al pulsar el botón "Destroy first component" verás que en tus dev tools se dispara el hook ngOnDestroy, ya que hemos eliminado el componente del DOM.
Cómo acceder a la template de un componente en distintas fases de su ciclo de vida
Si recordamos la sección del @ViewChild en la parte #2 de esta serie, éste servía para acceder al DOM a través de una propiedad del archivo TS de un componente y una local reference en el archivo HTML. Vamos a seguir trabajando en el ServerElementComponent, donde hemos probado todos los lifecycle hooks.
1. Queremos tener acceso al título de nuestra card, así que colocamos una local reference en el <div class="card-header"> del archivo server-element.component.html.
<div class="card-header" #heading>{{ name }}</div>
2. En el TSdel ServerElementComponent declaramos una variable (llamada header, por ejemplo) con el decorador @ViewChild para poder tener acceso al template. Recuerda que debemos pasarle la local reference como un argumento de tipo string y añadirle el objeto {static: true} como segundo parámetro.
También debemos importar @ViewChild y ElementRef desde angular/core. Recuerda que el ElementRef es el data type de nuestra propiedad.
3. Para ver en qué etapa de Angulartenemos acceso a esta propiedad, vamos a hacer un console.log en el ngOnInit del nativeElement del <div> que contiene la local reference. Como es un <div>, tenemos acceso a la propiedad textContent, así que la usamos para ver si en este primer hook se imprime.
Si guardas y exploras tus dev tools, verás que no es posible tener acceso a nuestra propiedad en el momento en el que el ngOnInit es llamado (mira cómo el textContent del console.log se imprime vacío en la consola).
Esto es así porque el div <div class="card-header" #heading>{{ name }}</div> es parte de la view del componente ServerElement. Y como hemos visto, la view de un componente se renderiza en la etapa del ngAfterViewInit, es decir, después del ngOnInit, no antes.
Si ahora coges el mismo código (el console.log) y lo pegas en el ngAfterViewInit, verás que ahora el código sí se imprime en la consola.
Cómo acceder al contenido proyectado con ngContent a través de @ContentChild
Ya hemos visto en qué etapa de los lifecycle hooks podemos acceder a las propiedades que usen @ViewChild. Ahora vamos a utilizar otra herramienta para un supuesto distinto: el caso en el que queramos acceder a contenido proyectado mediante ngContent.
Utilizamos @ContentChild para acceder a contenido de un componente X que se encuentra proyectado en un componente Z vía "ngContent".
¿Y dónde se da este caso? En el AppComponent, en cuya template tenemos el selector <app-server-element> con contenido proyectado desde el ServerElementComponent mediante el <ng-content>. El contenido al que queremos acceder es el que se encuentra dentro de las etiquetas de apertura y cierre del <app-server-element>. Aquí un esquemita aclarativo:
1. En el app.component.html, añadimos una local reference en el único <p> dentro del selector <app-server-element>, al que llamamos contentParagraph.
Lo que queremos es poder usar esa local reference en el ServerElementComponent. Para esto no podemos usar el ViewChild, sino el ContentChild, porque son dos cosas distintas: uno da acceso a la template (view) del componente cuyo contenido proyectamos, y otro al contenido proyectado con ngContent.
<p class="card-text" #contentParagraph>
2. En el archivo server-element.component.ts importamos @ContentChild desde angular/core y nos creamos la propiedad a la que le aplicaremos el @ContentChild. Al igual que con el decorador @ViewChild, la propiedad será de tipo ElementRef ya este decorador hay que pasarle dos parámetros:
Ten en cuenta que no podremos acceder a nuestra propiedad hasta que llegamos a cierta etapa: el ngAfterContentInit.
3. Si ahora hacemos un console.log en el ngOnInit
ngOnInit() {
console.log('ngOnInit was called!');
console.log('The content is:' + this.header.nativeElement.textContent);
console.log('The content of the paragraph is: ' + this.paragraph.nativeElement.textContent);
}
y en el ngAfterContentInit
ngAfterContentInit() {
console.log('ngAfterContentInit was called!');
console.log('The content of the paragraph is: ' + this.paragraph.nativeElement.textContent);
}
verás que el console.log del ngOnInit se imprime vacío, mientras que en el ngAfterContentInit se imprimirá el contenido del párrafo. Así, hemos demostrado que el contenido proyectado en un componente mediante ngContent sólo se ejecuta a partir de la etapa ngAfterContentInit.
¡FIN DE LA SERIE!
Y hasta aquí esta saga sobre el uso avanzado de componentes en Angular. Si te queda alguna duda, ¡nos vemos en los comentarios! Y si quieres seguir aprendiendo, aquí tienes un coding challenge sobre el tema
Sobre la autora de este post
Soy Rocío, una abogada reconvertida en programadora. Soy una apasionada de aprender cosas nuevas y ferviente defensora de que la única manera de ser feliz es alcanzando un equilibrio entre lo que te encanta hacer y lo que te saque de pobre. Mi historia completa, aquí.
Más recursos de aprendizaje
En mi experiencia, la manera más eficaz para aprender Angular es combinando varias vías de aprendizaje. Uno de mis métodos favoritos son los vídeo-cursos y mi plataforma predilecta para eso es Udemy. He hecho varios cursos pero sólo recomiendo aquellos que verdaderamente me han sido útiles. Aquí van:
Max Schwarzmüller
Fernando Herrera
Si necesitas apoyo en forma de libro, puede que éstos te sirvan de ayuda:
La programación es un mundo que evoluciona a una velocidad de vértigo. Los autores de estos libros lo saben, por eso suelen encargarse de actualizar su contenido regularmente. Asegúrate de que así sea antes de adquirirlos .
Participo en el programa de afiliados de Udemy y Amazon, lo que significa que, si compras alguno de estos cursos y/o libros, yo me llevaré una pequeña comisión y a ti no costará nada extra. Vamos, lo que se dice un win-win .
[tcb-script src="https://player.vimeo.com/api/player.js"][/tcb-script]A nadie le gusta su trabajo. Eso es lo que me decía a mí misma cuando conseguí mi primer[...]
Angular es un framework creado por Google que nos permite construir Single Page Applications (SPA, por sus siglas en inglés).Frameworks¿Pero qué es[...]
Esta web utiliza cookies para asegurar que se da la mejor experiencia al usuario. Si continúas utilizando este sitio se asume que estás de acuerdo. más información
Los ajustes de cookies en esta web están configurados para «permitir las cookies» y ofrecerte la mejor experiencia de navegación posible. Si sigues usando esta web sin cambiar tus ajustes de cookies o haces clic en «Aceptar», estarás dando tu consentimiento a esto.