Lifecycle hooks en Angular: lo que ya no necesitas (y qué usar en su lugar)

Lifecycle hooks en Angular: lo que ya no necesitas (y qué usar en su lugar)

Tiempo estimado de lectura: 4 min

Ideas clave

  • Signals y primitivas reactivas reemplazan la mayoría de los lifecycle hooks clásicos.
  • Convierte Inputs en señales y usa computed() para derivados; usa effect() para side-effects.
  • Usa DestroyRef.onDestroy() para limpieza cerca del recurso, evitando ngOnDestroy globales.
  • Reacciona al DOM con signal queries y afterNextRender() en lugar de los hooks after-view.

Tabla de contenidos

Si llevas años programando en Angular, algunos hooks clásicos te resultarán familiares: ngOnChanges, ngOnInit, ngAfterViewInit, ngOnDestroy… La plataforma no elimina el lifecycle, pero las nuevas primitivas (Signals, DestroyRef y utilidades de renderizado) mueven el enfoque hacia relaciones declarativas entre datos y componentes reaccionando a cambios en señales en lugar de comprobar “cuándo ocurre X”.

Resumen rápido (lectores con prisa)

Qué: Signals y primitivas reactivas reemplazan la mayor parte de los lifecycle hooks clásicos.

Cuándo: Úsalos al convertir Inputs, calcular derivados y gestionar side-effects que dependan de estado reactivo.

Por qué: Código más declarativo, menos errores de timing y mayor testabilidad.

Cómo: Inputs → señales; derivados → computed(); efectos → effect(); limpieza → DestroyRef.onDestroy().

Lifecycle hooks en Angular: qué dejar atrás y por qué

ngOnChanges → computed()

ngOnChanges estaba destinado a detectar cambios en @Input() y recalcular derivados. Con Signals, transforma inputs en señales y usa computed() para expresar derivados de forma declarativa.

price = input.required<number>();
tax   = input.required<number>();
total = computed(() => this.price() * (1 + this.tax()));

Por qué: menos código, cero bugs de sincronización, lecturas deterministas.

ngOnInit → constructor + effect() (o input())

ngOnInit fue el cajón de sastre. Los patrones modernos distribuyen responsabilidades:

  • Estado inicial: declara signal() o computed() en la propiedad.
  • Side-effects reactivos: usa effect() para reaccionar a señales.
  • Carga inicial ligada a inputs: usa helpers que disparan requests cuando el input está disponible.

Sigue usando ngOnInit solo si necesitas un efecto que explícitamente ocurra una vez y no dependa de reactividad (migraciones, integraciones legacy).

ngDoCheck → casi siempre obsoleto

Si creías necesitar ngDoCheck, para y replantea. ngDoCheck suele esconder un mal diseño. Signals cubren la detección fina sin chequeos manuales. Rediseña con señales y computed(); si aún necesitas inspección manual, documenta por qué.

ngAfterViewInit / ngAfterContentInit → signal queries + afterNextRender

Acceder a un ViewChild o ContentChild sin esperar el momento correcto fue una fuente interminable de bugs. Las Signal Queries (viewChild, contentChild) devuelven señales que cambian cuando el elemento existe.

chartEl = viewChild<ElementRef>('chart');

constructor() {
  effect(() => {
    const el = this.chartEl();
    if (el) { /* setup chart */ }
  });

  afterNextRender(() => {
    // ejecutar una vez después del primer render en cliente
  });
}

Combina con afterNextRender() para inicializar librerías del DOM (Chart.js, Leaflet) en cliente sin romper SSR.

ngAfterViewChecked → afterRender

Para reacciones tras cada render, afterRender() es la alternativa con semántica clara. Evita usarlo para lógica pesada; es para observabilidad y ajustes UI puntuales.

ngOnDestroy → DestroyRef

Olvida implementar OnDestroy en todas las clases. Inyecta DestroyRef y registra callbacks donde crees recursos. Esto vuelve la limpieza componible y cercana al lugar de creación del recurso.

const destroyRef = inject(DestroyRef);
const timer = setInterval(...);
destroyRef.onDestroy(() => clearInterval(timer));

Referencia: DestroyRef

Reglas prácticas de migración (lista corta, aplicable)

  1. Inputs → convierte a Signal Inputs; todos los cálculos derivados en computed().
  2. Efectos → usa effect() en constructor; evita suscripciones manuales cuando sea posible.
  3. DOM post-render → viewChild() + effect() para reactividad; afterNextRender() para inicializaciones DOM-only.
  4. Limpieza → DestroyRef.onDestroy() junto al recurso.
  5. Legacy → mantén hooks si tu integración depende de ellos; documenta el porqué.

Por qué importa (y cuándo no cambiar)

Menos hooks = menos magia oculta. Código más testable. Menos errores por timing. Pero hay excepciones: bibliotecas legacy, patterns altamente instrumentados, o equipos que necesitan migraciones graduales. En esos casos, planifica parques de migración: componente por componente.

Angular evoluciona hacia un modelo declarativo. Adoptar Signals no es solo reescribir métodos; es reorganizar la arquitectura mental del componente: de “controlador de eventos” a “descripción de relaciones”. El resultado: componentes más predecibles, más fáciles de probar y mantener.

Lecturas recomendadas

FAQ

 

¿Por qué debería convertir mis Inputs a señales?

Convertir Inputs a señales permite expresar de forma declarativa los cálculos derivados con computed() y reaccionar con effect(). Esto elimina la necesidad de logic dispersa en ngOnChanges y reduce errores de sincronización.

 

¿Cuándo sigue siendo válido usar ngOnInit?

Usa ngOnInit cuando necesites un efecto que debe ejecutarse exactamente una vez y no depende de la reactividad de señales (por ejemplo, migraciones o integraciones legacy). Para lógica ligada a estado reactivo, prefiere effect().

 

¿Cómo reemplazo las suscripciones manuales?

Siempre que sea posible, reemplaza suscripciones con effect() o convierte fuentes externas en señales. Para recursos que aún usan callbacks o timers, registra limpieza con DestroyRef.onDestroy() junto al recurso.

 

¿Qué uso para inicializaciones que requieren el DOM?

Usa signal queries (viewChild(), contentChild()) que devuelven señales y combínalas con effect(). Para inicializaciones que deben ocurrir solo en cliente después del primer render, utiliza afterNextRender().

 

¿Qué ventajas tiene DestroyRef frente a ngOnDestroy?

DestroyRef permite registrar callbacks de limpieza cerca del recurso, haciendo la limpieza composable y localizada. Evita la necesidad de implementar la interfaz OnDestroy en cada clase y mejora la trazabilidad de recursos.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *