Skip to content

🔄 Clase 4 — Ciclo de Vida

📅 2026-07-18 · 🗂️ Proyecto: AppClase04 (destino en este repo: app-semana04) · 🎯 Ciclo de vida de un componente

Nuevo proyecto para el tema del día: el ciclo de vida de un componente — los momentos por los que pasa un componente Angular desde que se crea hasta que se destruye.

➡️ El mismo día se vio después Eventos en Angular (Event Binding, categorías de eventos) → ver Clase 5.

🗂️ Temas de esta clase

  1. Nuevo proyecto AppClase04, componente saludo generado con Angular CLI dentro de una subcarpeta component/.
  2. Ciclo de vida de un componente: constructor()ngOnChanges()ngOnInit() → (…) → ngOnDestroy(), con console.log numerados para ver el orden real.
  3. @Input(): pasar un dato del componente padre (App) a uno hijo (Bienvenida), y cómo eso dispara ngOnChanges.

🆕 El proyecto y el componente saludo

  • Carpeta del proyecto: ~/angular/AppClase04 (en la máquina del curso). Todavía no está copiado a 02-Ejercicios/app-semana04/ de este repo — pendiente para cuando termine la clase.

  • Comando usado:

    zsh · AppClase04
    $ ng generate component component/saludo
    CREATE src/app/component/saludo/saludo.spec.ts (554 bytes)
    CREATE src/app/component/saludo/saludo.ts (197 bytes)
    CREATE src/app/component/saludo/saludo.css (0 bytes)
    CREATE src/app/component/saludo/saludo.html (22 bytes)
  • Carpeta component/ como contenedor: en vez de crear el componente directo en src/app/saludo/, el profe lo pidió dentro de una subcarpeta component/ (src/app/component/saludo/). Es una forma de agrupar todos los componentes hijos aparte del componente raíz (app.ts), útil cuando el proyecto crece y tiene varios.

  • Nomenclatura moderna: los archivos se llaman saludo.ts / saludo.html / saludo.css (sin el sufijo .component), igual que el componente raíz app.ts. → ver 05-Estructura-proyecto.md (sección de nomenclatura moderna vs antigua).

  • saludo.html (primer contenido, texto cortado en la captura):

    html
    <p>¡Hola! soy un componente y vamos a ver el ciclo de vida...</p>

    📝 Correcciones de la pizarra/código: faltaba el signo de apertura (!Hola¡Hola) y había una errata avmosvamos. El texto sigue cortado en la captura (...ciclo de v) — falta ver cómo termina la frase.

🧩 saludo.ts: la clase implementa OnInit y OnDestroy

🗺️ Diagrama: el orden de los hooks

Antes de ver el código, el mapa completo de por dónde pasa un componente desde que nace hasta que se destruye — ngOnChanges solo aparece si el componente tiene @Input() (no es el caso de saludo, sí el de bienvenida más abajo):

Diagrama de flujo del ciclo de vida de un componente Angular: constructor, decisión sobre si tiene @Input con cambios, ngOnChanges (antes de ngOnInit y de nuevo en cada cambio futuro), ngOnInit una sola vez, el componente en uso esperando cambios, y ngOnDestroy antes de que Angular destruya el componente

  • constructor() corre primero y no es un hook de Angular (es de JS/TS puro) — todavía no hay acceso a los @Input().
  • ngOnChanges() solo se dispara si el componente tiene algún @Input(): la primera vez corre antes de ngOnInit(), y se repite en cada cambio posterior (la flecha punteada que vuelve hacia atrás en el diagrama).
  • ngOnInit() corre una sola vez, ya con los @Input() disponibles.
  • ngOnDestroy() corre justo antes de que Angular quite el componente de la pantalla (p. ej. al cambiar de ruta o al ocultarlo con @if).
ts
import { Component, OnInit, OnDestroy } from '@angular/core';

@Component({
  selector: 'app-saludo',
  imports: [],
  templateUrl: './saludo.html',
  styleUrl: './saludo.css',
})
export class Saludo implements OnInit, OnDestroy {

  constructor() {
    console.log('1. Naci (contructor)');
  }

  ngOnInit(): void {
    console.log('2. Ya estoy listo (ngOnInit)');
  }

  ngOnDestroy(): void {
    console.log('3. Ya estoy listo (ngOnInit)'); // 📝 ver nota de abajo, etiqueta errada
  }

}

📝 Erratas en la captura: Naci(contructor) → debería ser Nací (constructor) (falta tilde y le falta una s a "constructor"; se deja tal cual porque así quedó en el código real). El mensaje de ngOnInit() ya se corrigió a "2. Ya estoy listo (ngOnInit)" (ya no repite el texto del constructor). Pero el de ngOnDestroy() quedó con la misma etiqueta (ngOnInit) en vez de (ngOnDestroy) — de nuevo pinta a copy-paste sin terminar de editar. Pendiente ver si se corrige.

  • OnInit y OnDestroy son interfaces de @angular/core: cada una obliga a implementar un método con un nombre exacto (ngOnInit() y ngOnDestroy()) si la clase dice implements esa interfaz. Son 2 de los hooks del ciclo de vida — funciones que Angular llama automáticamente en momentos concretos de la vida del componente.
  • El constructor() NO es un hook de Angular — es de JavaScript/TypeScript puro: se ejecuta al crear el objeto (new Saludo()), antes de que Angular haga nada con el componente (antes de leer sus @Input, antes de pintar el HTML). Por eso el profe lo numera como el paso 1 ("Nací") en la línea de tiempo del ciclo de vida, aunque técnicamente no sea parte de la interfaz OnInit/OnDestroy.
  • El mensaje console.log numerado ('1. Nací (constructor)') es la pista de que el profe va a ir numerando cada paso del ciclo de vida conforme aparezcan ngOnInit() (¿"2. Crecí"?) y ngOnDestroy() (¿"Morí"?) — pendiente confirmar los textos exactos en la próxima captura.

🧪 Tip de entrevista: ¿constructor() es un hook del ciclo de vida de Angular? → No. Es el constructor de la clase (JS/TS), se ejecuta antes que cualquier hook de Angular. El primer hook real del ciclo de vida es ngOnInit().

🔌 app.html: se usan los componentes saludo y bienvenida

html
<h3>Hola</h3>

<app-saludo></app-saludo>
<app-bienvenida></app-bienvenida>
  • <app-saludo> y <app-bienvenida> son los selectores declarados en cada @Component. Para que compile, Saludo y Bienvenida deben estar en el array imports: [...] de App (app.ts) — son componentes standalone, se importan como cualquier otro, no hace falta un NgModule.
  • En cuanto aparecen en pantalla, Angular crea cada componente → dispara la secuencia de console.log de su ciclo de vida en la consola del navegador.

🧪 Tip de entrevista: ¿Para qué sirve implements OnInit si igual podría escribir ngOnInit() sin la interfaz? → Angular lo llama igual sin la interfaz (por convención de nombre), pero implements le pide a TypeScript que verifique en compilación que el método existe y tiene la firma correcta. Es una red de seguridad, no un requisito en tiempo de ejecución.

🙋 Segundo componente: bienvenida (uso real de ngOnInit + @Input + ngOnChanges)

Mientras saludo demuestra el orden de los hooks (constructor → ngOnInit → ngOnDestroy), bienvenida muestra para qué sirve ngOnInit en la práctica, y suma un tercer hook: ngOnChanges, que se dispara cuando cambia un @Input().

ts
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';

@Component({
  selector: 'app-bienvenida',
  imports: [],
  templateUrl: './bienvenida.html',
  styleUrl: './bienvenida.css',
})
export class Bienvenida implements OnChanges, OnInit {

  mensaje = '';

  @Input() nombre = '';

  constructor() {
    console.log('naci');
  }

  ngOnChanges(changes: SimpleChanges): void {
    console.log('entoy en ngOnChanges');
    console.log('el nombre cambio a :', changes['nombre'].currentValue);
  }

  ngOnInit(): void {
    this.mensaje = 'Bienvenido al curso';
    console.log('entre al ngOnInit');
  }
}
html
<p>{{ mensaje }}</p>
<p>{{ nombre }}</p>
  • @Input() nombre = '' recibe un dato del componente padre (App), por eso necesita import { ... Input ... } — errata típica al escribir esto rápido: olvidar agregar Input al import y quedarse con el error "Cannot find name 'Input'".
  • ngOnChanges(changes: SimpleChanges) se dispara cada vez que Angular detecta un cambio en un @Input() — incluida la primera vez que llega su valor inicial. changes['nombre'].currentValue da el valor nuevo (y changes['nombre'].previousValue el anterior, aunque no se usó aquí).
  • mensaje = '' arranca vacío (a propósito, para notar el cambio); ngOnInit() recién ahí le pone el valor real ('Bienvenido al curso').

🔢 Orden real confirmado en la consola del navegador

DevTools · Console
naci
entoy en ngOnChanges
el nombre cambio a : <valor>
entre al ngOnInit

constructorngOnChangesngOnInit. Esto confirma la regla de Angular: si el componente tiene @Input(), ngOnChanges se ejecuta antes que ngOnInit (Angular necesita entregar los @Input antes de avisar "ya terminé de inicializar"). Si el componente no tuviera ningún @Input, ngOnChanges nunca se llamaría (como pasa con saludo, que no recibe datos del padre).

🧪 Tip de entrevista: ¿En qué orden se ejecutan ngOnChanges y ngOnInit? → ngOnChanges siempre antes, y solo si el componente declara al menos un @Input(). ngOnInit se ejecuta una sola vez; ngOnChanges se repite cada vez que cambia algún @Input (incluida la primera entrega de su valor).

🔗 app.ts / app.html: el @Input en acción (padre → hijo)

ts
import { Component, signal } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { Saludo } from './component/saludo/saludo';
import { Bienvenida } from './component/bienvenida/bienvenida';
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-root',
  imports: [RouterOutlet, Saludo, Bienvenida, FormsModule],
  templateUrl: './app.html',
  styleUrl: './app.css',
})
export class App {
  protected readonly title = signal('app-semana04');
  nombreEscrito = '';
}
html
<input [(ngModel)]="nombreEscrito" placeholder="Ingrese su nombre" />
<app-bienvenida [nombre]="nombreEscrito"></app-bienvenida>
  • nombreEscrito es una propiedad normal (no un signal()): [(ngModel)] necesita poder asignarle un valor directo con =, y un signal no se actualiza así (se actualiza con .set()), así que mezclarlos rompe el two-way binding.
  • [(ngModel)]="nombreEscrito" (Two-way Binding) actualiza nombreEscrito en cada tecla escrita en el <input>.
  • [nombre]="nombreEscrito" (property binding, un solo sentido) le pasa ese valor al @Input() nombre de Bienvenida. Cada vez que nombreEscrito cambia, Angular vuelve a disparar ngOnChanges en Bienvenida — de ahí que escribir en el input dispare el console.log de ngOnChanges una vez por cada letra.

⚠️ Erratas que salieron al armar esto: una comilla de más en [nombre] = "nombreEscrito"" (rompía el parseo del HTML — error NG5002), y usar signal('') junto con [(ngModel)] (no compatibles entre sí). Ambas corregidas arriba.

▶️ Cómo correrlo (ya armado en este repo)

Se creó el proyecto real en 02-Ejercicios/app-semana04/ (mismo patrón que las clases anteriores), con los componentes saludo y bienvenida ya cableados en app.html (también viven ahí envio e informacion, de la segunda parte de la clase → ver eventos-en-angular.md):

zsh · app-semana04
$ cd 02-Ejercicios/app-semana04
$ npm install     // solo la primera vez
$ npm start       // = ng serve → http://localhost:4200/

Abre esa URL, abre la consola del navegador (DevTools → Console) y ahí se ven los console.log numerados del ciclo de vida de saludo y el de bienvenida.

🏋️ Ejercicios con solución

Ejercicio 1 — corregir la etiqueta de ngOnDestroy() en saludo.ts

El código real de saludo.ts (visto más arriba) tiene una errata ya anotada con 📝: el console.log de ngOnDestroy() quedó con el mismo texto (ngOnInit) en vez de (ngOnDestroy), como si fuera un copy-paste sin terminar de editar. Corrígela sin tocar el número de paso ('3. ...').

Ver solución
ts
import { Component, OnInit, OnDestroy } from '@angular/core';

@Component({
  selector: 'app-saludo',
  imports: [],
  templateUrl: './saludo.html',
  styleUrl: './saludo.css',
})
export class Saludo implements OnInit, OnDestroy {

  constructor() {
    console.log('1. Naci (contructor)');
  }

  ngOnInit(): void {
    console.log('2. Ya estoy listo (ngOnInit)');
  }

  ngOnDestroy(): void {
    console.log('3. Ya me destrui (ngOnDestroy)'); // 👈 etiqueta corregida
  }

}

Ejercicio 2 — agregar @Input() + ngOnChanges a saludo (mismo tratamiento que bienvenida)

saludo hoy solo implementa OnInit y OnDestroy, por eso nunca se dispara ngOnChanges (no tiene ningún @Input()). Dale a Saludo un @Input() nombre = '' y un ngOnChanges con console.log numerado, siguiendo el patrón de bienvenida.ts. Pista: al sumar un hook nuevo entre el constructor y ngOnInit(), hay que renumerar los pasos siguientes.

Ver solución
ts
import { Component, Input, OnChanges, OnInit, OnDestroy, SimpleChanges } from '@angular/core';

@Component({
  selector: 'app-saludo',
  imports: [],
  templateUrl: './saludo.html',
  styleUrl: './saludo.css',
})
export class Saludo implements OnChanges, OnInit, OnDestroy {

  @Input() nombre = '';

  constructor() {
    console.log('1. Naci (contructor)');
  }

  ngOnChanges(changes: SimpleChanges): void {
    console.log('2. Cambio el @Input (ngOnChanges):', changes['nombre'].currentValue);
  }

  ngOnInit(): void {
    console.log('3. Ya estoy listo (ngOnInit)');
  }

  ngOnDestroy(): void {
    console.log('4. Ya me destrui (ngOnDestroy)');
  }

}

Para que ngOnChanges realmente se dispare, app.html tiene que pasarle un valor por property binding, igual que con bienvenida:

html
<app-saludo [nombre]="nombreEscrito"></app-saludo>

❓ Preguntas y respuestas

1. ¿constructor() es un hook del ciclo de vida de Angular?

No. Es el constructor de la clase (JS/TS puro) y corre antes que cualquier hook de Angular — todavía no hay acceso a los @Input(). El primer hook real del ciclo de vida es ngOnInit().

2. ¿Para qué sirve implements OnInit si igual podría escribir ngOnInit() sin la interfaz?

Angular lo llama igual (por convención de nombre), pero implements le pide a TypeScript que verifique en compilación que el método existe con la firma correcta. Es una red de seguridad, no un requisito en tiempo de ejecución.

3. ¿En qué orden se ejecutan ngOnChanges y ngOnInit?

ngOnChanges siempre antes, y solo si el componente declara al menos un @Input(). ngOnInit se ejecuta una sola vez; ngOnChanges se repite cada vez que cambia algún @Input (incluida la primera entrega de su valor).

4. ¿Cuándo se dispara ngOnChanges por primera vez?

En cuanto Angular entrega el valor inicial del @Input(), incluso si ese valor no "cambió" desde ningún estado anterior (el componente recién se está creando). Por eso en bienvenida ya aparece ngOnChanges en el primer render, antes de escribir nada en el <input>.

❓ Pendiente para completar esta clase

  • [ ] Ver el resto de la frase en saludo.html.
  • [x] Cuerpo real de constructor(), ngOnInit() y ngOnDestroy() — los 3 ya tienen su console.log numerado (1, 2, 3).
  • [ ] Confirmar si se corrige la etiqueta (ngOnInit) repetida en el console.log de ngOnDestroy().
  • [x] ngOnChanges confirmado en bienvenida (se agregó @Input() nombre + un <input> con [(ngModel)] en App para dispararlo). Orden real: constructor → ngOnChanges → ngOnInit.
  • [ ] Confirmar si se ven más hooks (ngAfterViewInit, ngDoCheck, etc.).
  • [ ] Ver cómo/cuándo se dispara ngOnDestroy() en la demo (normalmente al quitar el componente de la pantalla, p. ej. con @if o cambiando de ruta).
  • [x] Agregar el concepto "Ciclo de vida de un componente" a 02-Conceptos.md.
  • [x] Armar el proyecto real en 02-Ejercicios/app-semana04/ de este repo.

📎 Apuntes relacionados

  • 05-Estructura-proyecto.md — nomenclatura moderna de archivos de componente.
  • 02-Conceptos.md (sección Ciclo de vida de un componente) — tabla resumen de los hooks y el porqué de usar ngOnInit en vez del constructor.

➡️ Siguiente

Seguir viendo el código del ciclo de vida (posible corrección del console.log de ngOnDestroy(), y si aparecen más hooks) a medida que el profe lo explique.