I componenti che costituiscono una applicazione Angular 2 vengono creati dinamicamente in base all'evoluzione dell'applicazione stessa. L'interazione con l'utente e con il server avvia la creazione dei componenti, la loro visualizzazione ed il loro aggiornamento sulla schermata, la loro distruzione. L'esistenza dei componenti durante l'esecuzione dell'applicazione attraversa diverse fasi che ne rappresentano il ciclo di vita. Angular 2 ci consente di intercettare e gestire in maniera personalizzata le varie fasi del ciclo di vita di un componente sfruttando i cosiddetti Lifecycle Hooks: un insieme di eventi in corrispondenza dei quali è possibile definire dei metodi per la loro gestione.
Prima di ogni fase: l'esecuzione del costruttore
Prima di vedere nel dettaglio quali sono le fasi del ciclo di vita dei componenti, è opportuno evidenziare che la prima attività effettuata dal framework alla creazione di un componente è l'esecuzione del suo costruttore. Anche se tecnicamente non rappresenta un Lifecycle Hook, l'esecuzione del costruttore è quindi la fase iniziale della creazione di un componente Angular2. È da evidenziare tuttavia che in questa fase:
- non sono ancora state inizializzate le proprietà di input;
- non è ancora disponibile la view associata al componente stesso.
Perciò non è possibile accedere agli elementi del DOM e ai componenti figli. La fase di esecuzione del costruttore rappresenta il momento della creazione dell'istanza del componente ed è quindi utilizzabile per effettuare quelle inizializzazioni che non riguardano il rendering grafico e l'interazione con il framework.
Le fasi del ciclo di vita del componente
Dopo l'esecuzione del costruttore, le seguenti fasi si susseguono in sequenza temporale:
Campo | Descrizione |
---|---|
OnChanges | Si verifica quando il valore di una proprietà di input viene modificato. Oltre a verificarsi prima dell'inizializzazione del componente, si verifica anche ogni qualvolta cambia il valore delle proprietà di input |
OnInit | Rappresenta la fase di inizializzazione del componente e si verifica dopo il primo evento OnChanges .Questa fase viene eseguita una sola volta durante il ciclo di vita del componente. |
DoCheck | Questa fase viene eseguita durante il check interno di Angular per valutare le modifiche ai componenti ed ai dati. Approfondiremo questo aspetto più avanti nella guida. |
AfterContentInit | In questa fase il contenuto associato al componente è stato inizializzato; in particolare, è stato costruito l'albero degli eventuali componenti figli. |
AfterContentChecked | Anche questa fase viene eseguita durante un check interno di Angular sui contenuti associati al componente. Come per la fase di DoCheck , essa risulterà più chiara quando parleremo in maniera più dettagliata del processo di change detection di Angular |
AfterViewInit | Questa è la fase di inizializzazione della view associata al componente. In questa fase il componente risulta mappato sul DOM ed è quindi visibile. |
AfterViewChecked | Come per le altre fasi checked, anche in questo caso questa fase riguarda il check interno di Angular sulla view appena generata. |
OnDestroy | Questa è l'ultima fase del ciclo di vita del componente e si verifica prima che Angular lo distrugga definitivamente. Questa fase viene eseguita una sola volta durante il ciclo di vita del componente. |
In corrispondenza a ciascuna di queste fasi possiamo scrivere il nostro codice per personalizzare il comportamento standard del ciclo di vita del nostro componente. Ad esempio, potremmo intercettare le modifiche ad una proprietà di input per analizzare la variazione del valore e gestirle opportunamente oppure potremmo decidere se visualizzare o meno un elemento del componente previsto dal suo template in base ad una logico più o meno complessa.
Gestire le fasi del component lifecycle
Indipendentemente da cosa e come vogliamo gestire una o più fasi del ciclo di vita di un componente, l'approccio generale consiste nell'implementare una specifica interfaccia TypeScript il cui nome corrisponde alla fase da gestire. Ad esempio, se intendiamo gestire la fase OnInit di un componente dobbiamo implementare l'omonima interfaccia come mostrato di seguito:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'articolo',
templateUrl: 'articolo.component.html',
styleUrls: ['articolo.component.css']
})
export class ArticoloComponent implements OnInit {
constructor() { }
ngOnInit() {
console.log("Il componente è in fase di inizializzazione!");
}
}
Rispetto al codice visto negli esempi precedenti possiamo notare l'importazione dell'interfaccia OnInit
dalla libreria @angular/core
del framework e la dichiarazione di implementazione tramite la clausola implements OnInit
. Notiamo anche la presenza del metodo ngOnInit()
che rappresenta proprio l'implementazione dell'interfaccia OnInit
.
Tutte le interfacce corrispondenti alle fasi del ciclo di vita dei componenti prevedono un solo metodo il cui nome è costituito dal nome della fase preceduto dal prefisso ng
. Avremo quindi ngOnChanges()
per la gestione della fase OnChanges
, ngAfterViewInit()
per la gestione di AfterViewInit
e così via.
Proviamo a fare un esempio sull'intercettazione delle fasi del ciclo di vita dei componenti e riprendiamo il nostro progetto sulla visualizzazione del testo di un articolo. Supponiamo di voler gestire la codifica di caratteri speciali all'interno del testo. Per chiarire, vorremmo che caratteri come le lettere accentate, dieresi, cediglie ed altro venissero visualizzati correttamente all'interno della view associata al componente e non sostituiti da caratteri strani come nell'esempio mostrato dalla seguente figura:
In JavaScript possiamo ottenere ciò utilizzando la codifica esadecimale. Ad esempio, possiamo visualizzare il carattere è
tramite la rappresentazione esadecimale \xE9
.
Per semplicità ci concentreremo proprio sulla sostituzione del carattere è
all'interno del testo dell'articolo ed utilizzeremo un approccio che intercetta la fase OnChanges del ciclo di vita del nostro componente. Riscriveremo dunque il componente ArticoloComponent
come mostrato di seguito:
import { Component, Input, OnChanges, SimpleChange } from '@angular/core';
import { Articolo } from './articolo'
@Component({
selector: 'articolo',
templateUrl: 'articolo.component.html',
styleUrls: ['articolo.component.css']
})
export class ArticoloComponent implements OnChanges {
@Input() articolo: Articolo;
ngOnChanges(changes: {[propertyName: string]: SimpleChange}) {
if (changes["articolo"] && changes["articolo"].currentValue.testo) {
let testoArticolo = changes["articolo"].currentValue.testo;
changes["articolo"].currentValue.testo = testoArticolo.replace("è", "\xE9") ;
}
}
}
Sempre per semplicità, abbiamo riportato soltanto le parti del codice del componente rilevanti per quanto vogliamo discutere.
Notiamo subito l'importazione dell'interfaccia OnChanges
e della classe SimpleChange
, che descriveremo tra breve. Vediamo anche la dichiarazione dell'implementazione dell'interfaccia OnChanges
e la presenza del metodo ngOnChanges()
che concretizza tale implementazione.
Il metodo ngOnChanges()
prevede il parametro changes
la cui struttura è quella di un oggetto con un numero indefinito di proprietà di tipo SimpleChange
. L'oggetto changes
rappresenta l'elenco delle proprietà di input del componente che sono state modificate. Nel caso del nostro componente abbiamo una sola proprietà di input, articolo
, quindi il parametro changes
potrà avere al massimo una proprietà articolo
con la struttura definita dalla classe SimpleChange
.
La classe SimpleChange
prevede due proprietà che ci forniscono informazioni sulle variazioni avvenute: previousValue
, che rappresenta il valore che la proprietà osservata aveva prima della modifica, e currentValue
, che rappresenta il nuovo valore assegnato alla proprietà di input.
Il codice del metodo ngOnChanges()
si occupa quindi di verificare se tra le modifiche riportate ce n'è una che coinvolge la proprietà di input articolo
e se l'articolo ha la proprietà testo
(quest'ultima verifica ci assicura che la proprietà articolo
è stata correttamente inizializzata). In caso positivo andiamo a sostituire ogni occorrenza del carattere è
con il valore esadecimale \xE9
ed otteniamo il risultato desiderato:
L'estensione di questo approccio per la sostituzione di altri caratteri speciali dovrebbe risultare abbastanza immediato una volta individuati i codici esadecimali corrispondenti.