Ciclo di vita dei componenti Angular: guida completa ai lifecycle hooks

Il ciclo di vita dei componenti è uno dei concetti fondamentali da padroneggiare per sviluppare applicazioni Angular efficaci. In questa guida esploreremo tutti i lifecycle hooks disponibili, il loro ordine di esecuzione e come utilizzarli in scenari reali.
Cos’è il ciclo di vita di un componente
Ogni componente Angular attraversa diverse fasi durante la sua esistenza: creazione, inizializzazione, rilevamento dei cambiamenti e distruzione. Angular fornisce degli “hook” (tradotto in “ganci”, nel senso di “dritta”) che permettono di intercettare questi momenti specifici ed eseguire logica personalizzata.
Lifecycle Hooks
ngOnChanges
Quando viene chiamato: Prima di ngOnInit
, dopo il costruttore, e ogni volta che cambiano le proprietà di input del componente. In altre parole, viene spesso utilizzato con le proprietà decorate con @Input
.
Scopo: Gestire i cambiamenti delle proprietà di input e reagire di conseguenza.
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
@Component({
selector: 'app-user-profile',
template: `
<div>
<h2>{{userName}}</h2>
<p>Ultimo aggiornamento: {{lastUpdate}}</p>
</div>
`
})
export class UserProfileComponent implements OnChanges {
@Input() userName: string = '';
lastUpdate: string = '';
ngOnChanges(changes: SimpleChanges): void {
if (changes['userName']) {
console.log('Username cambiato:', changes['userName'].currentValue);
this.lastUpdate = new Date().toLocaleTimeString();
}
}
}
ngOnInit
Quando viene chiamato: Una sola volta, dopo il primo ngOnChanges
.
Scopo: Inizializzazione del componente, caricamento di dati iniziali.
import { Component, OnInit } from '@angular/core';
import { UserService } from './user.service';
@Component({
selector: 'app-user-list',
template: `
<div *ngIf="loading">Caricamento...</div>
<ul *ngIf="!loading">
<li *ngFor="let user of users">{{user.name}}</li>
</ul>
`
})
export class UserListComponent implements OnInit {
users: any[] = [];
loading = true;
constructor(private userService: UserService) {}
ngOnInit(): void {
this.loadUsers();
}
private loadUsers(): void {
this.userService.getUsers().subscribe({
next: (users) => {
this.users = users;
this.loading = false;
},
error: (error) => {
console.error('Errore nel caricamento utenti:', error);
this.loading = false;
}
});
}
}
ngDoCheck
Quando viene chiamato: Durante ogni ciclo di rilevamento dei cambiamenti.
Scopo: Implementare la logica che rileva eventuali cambiamenti che Angular non rileva automaticamente.
import { Component, DoCheck, Input } from '@angular/core';
@Component({
selector: 'app-custom-detector',
template: `<p>Elementi nell'array: {{items.length}}</p>`
})
export class CustomDetectorComponent implements DoCheck {
@Input() items: any[] = [];
private lastItemsLength = 0;
ngDoCheck(): void {
if (this.items.length !== this.lastItemsLength) {
console.log('Cambiamento rilevato nell\'array items');
this.lastItemsLength = this.items.length;
}
}
}
ngAfterContentInit
Quando viene chiamato: Dopo che Angular ha caricato completamente il contenuto esterno nel componente, ossia il contenuto proiettato con <ng-content>
.
Scopo: Gestire la logica che dipende dal contenuto, e quindi dalla proiezione di contenuti. Per contenuto si intende ciò che viene inserito all’interno del componente da un altro componente, e non il template del componente stesso.
import { Component, AfterContentInit, ContentChild, ElementRef } from '@angular/core';
@Component({
selector: 'app-content-wrapper',
template: `
<div class="wrapper">
<ng-content></ng-content>
</div>
`
})
export class ContentWrapperComponent implements AfterContentInit {
@ContentChild('projectedElement') projectedElement!: ElementRef;
ngAfterContentInit(): void {
if (this.projectedElement) {
console.log('Elemento nella vista inizializzato:', this.projectedElement.nativeElement);
}
}
}
ngAfterViewInit
Quando viene chiamato: Dopo che Angular ha inizializzato completamente la view del componente e le view dei componenti figli.
Scopo: Manipolazione del DOM, inizializzazione di librerie esterne.
import { Component, AfterViewInit, ViewChild, ElementRef } from '@angular/core';
@Component({
selector: 'app-chart',
template: `<canvas #chartCanvas width="400" height="200"></canvas>`
})
export class ChartComponent implements AfterViewInit {
@ViewChild('chartCanvas') canvas!: ElementRef<HTMLCanvasElement>;
ngAfterViewInit(): void {
this.initializeChart();
}
private initializeChart(): void {
const ctx = this.canvas.nativeElement.getContext('2d');
if (ctx) {
// Inizializzazione di una libreria di grafici come Chart.js
console.log('Canvas pronto per il rendering del grafico');
}
}
}
ngAfterViewInit vs ngAfterContentInit: Il primo si occupa della view del componente e dei suoi figli, mentre il secondo si occupa del contenuto proiettato nel componente. Nella pratica,
ngAfterViewInit
viene usato più frequentemente per manipolare il DOM, mentrengAfterContentInit
è utile quando si lavora con componenti che utilizzano<ng-content>
.
ngOnDestroy
Quando viene chiamato: Poco prima che Angular distrugga il componente.
Scopo: Pulizia delle risorse, cancellazione di subscriptions, timer, event listeners.
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription, interval } from 'rxjs';
@Component({
selector: 'app-timer',
template: `<p>Timer: {{counter}}</p>`
})
export class TimerComponent implements OnInit, OnDestroy {
counter = 0;
private timerSubscription?: Subscription;
ngOnInit(): void {
this.timerSubscription = interval(1000).subscribe(() => {
this.counter++;
});
}
ngOnDestroy(): void {
// Importante: cancellare la subscription per evitare memory leaks
if (this.timerSubscription) {
this.timerSubscription.unsubscribe();
}
}
}
Ordine di esecuzione dei Lifecycle Hooks
Questi hooks hanno un ordine specifico di esecuzione durante il ciclo di vita del componente:
ngOnChanges
(se ci sono input properties)ngOnInit
ngDoCheck
ngAfterContentInit
ngAfterContentChecked
ngAfterViewInit
ngAfterViewChecked
ngDoCheck
(ripetuto ad ogni change detection)ngAfterContentChecked
(ripetuto ad ogni change detection)ngAfterViewChecked
(ripetuto ad ogni change detection)ngOnDestroy
(alla distruzione del componente)
Rappresentazione grafica del ciclo di vita degli hooks in Angular (Credits: https://miloszeljko.com)
Come visibile anche dall’immagine, ci sono una serie di hooks che vengono richiamati ad ogni ciclo di change detection (ngDoCheck
, ngAfterContentChecked
, ngAfterViewChecked
). Questi hooks sono utili per eseguire logiche che devono essere verificate frequentemente, ma è importante usarli con cautela per evitare problemi di performance, considerato che ogni cambiamento nello stato dell’applicazione (ad esempio un evento utente o una risposta HTTP) può innescare un ciclo di change detection.