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

Immagine di copertina

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, mentre ngAfterContentInit è 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:

  1. ngOnChanges (se ci sono input properties)
  2. ngOnInit
  3. ngDoCheck
  4. ngAfterContentInit
  5. ngAfterContentChecked
  6. ngAfterViewInit
  7. ngAfterViewChecked
  8. ngDoCheck (ripetuto ad ogni change detection)
  9. ngAfterContentChecked (ripetuto ad ogni change detection)
  10. ngAfterViewChecked (ripetuto ad ogni change detection)
  11. 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.

Conosci meglio chi ha scritto questo articolo

Serena Sensini

Ciao! Mi chiamo Serena Sensini e sono la creatrice di @ TheRedCode.it. Ho aperto questo blog nel 2021 per raccontare il mio lavoro e il mondo dell’informatica a parole semplici, in piccole pillole e alla portata di tutte le persone.

Sono un’ingegnera informatica specializzata in ambito AI & NLP. Di giorno lavoro come CTO @ Welyk e come Innovation & Emerging Technologies Leader @ Dedalus, mentre di notte scrivo e sono autrice di 5 libri -per ora-. 🖊️

Foto di Serena Sensini

Partners

Community, aziende e persone che supportano attivamente il blog

Vuoi diventare partner?

Collaboriamo con community, aziende e strumenti del mondo tech che vogliono raggiungere la nostra community di dev ed engineer italiani. Scrivici per proporre una collaborazione.

✉️ Scrivici

Vuoi diventare tech content creator? 🖊️

Se ti va di raccontare la tua esperienza nel mondo tech, questo è il posto giusto.

Cerchiamo voci autentiche, esempi pratici e punti di vista utili per chi legge.

Scrivici a collaborazioni[at]theredcode.it con una proposta: idea, taglio del contenuto e una breve presentazione. Non vediamo l'ora di leggere la tua esperienza!

Invia la tua idea