Angular day 2018, ep. 3: Francesco Sciuti a proposito di Change Detection, Zone.js e altri demoni in agguato nei recessi più oscuri di Angular

Angular day 2018, Francesco Sciuti ci spaventa con il suo talk Change Detection, Zone.js ed altri mostri.

Questo articolo non vuole in alcun modo essere una trattazione esaustiva del modello di Change Detection e di zoning di Angular, ma solo un’anticipazione.

Zone.js

il concetto di “Zone” è stato in qualche modo ereditato dall’omonima feature di Dart (diversi ingegneri in Google stanno usando AngularDart nelle loro app oggi), dove viene così definita:

A zone represents the asynchronous dynamic extent of a call. It is the computation that is performed as part of a call and, transitively, the asynchronous callbacks that have been registered by that code.

In sintesi una zona è un contesto di esecuzione per operazioni asincrone. Per spiegare cosa questo voglia dire però, è prima necessaria una breve digressione sul concetto di Stack in Javascript.

Call Stack

Non mi aspetto di rivelare niente di nuovo dicendo che JavaScript è un linguaggio mono-thread. Ciò significa che tutte le operazioni vengono svolte all’interno di un unico thread e che quindi esiste un unico Call Stack. il Call Stack non è altro che una struttura che tiene traccia di quale punto del programma si trovi l’esecuzione. Quindi 1 thread = 1 Call Stack = un’operazione alla volta. Quando si entra in una funzione, questa viene posta “in cima” allo stack; quando se ne esce, questa viene tolta dallo stack. Ogni entry nel Call Stack viene chiamata Stack Frame (i cubetti grigi in fig.), e questo è come JavaScript riesce a costruire lo Stack Trace quando si genera un errore1. Fondamentalmente il Call Stack non è altro che una coda LIFO (Last In-First Out), le cui uniche capacità sono quelle di mettere l’ultima chiamata in cima alla pila, e prendere l’ultima chiamata dalla cima della pila.

Stato del Call Stack man mano che le funzioni vengono chiamate e terminate

Ora, gli engine di esecuzione di JavaScript come ad esempio Node.js eseguono ciascuna funzione nel proprio stack frame, e in un ambiente Javascript “standard” non c’è alcuna relazione tra i vari frame. Lo stack può essere astratto con una figura di questo genere, con 3 frame. Questo è il caso “standard”, dove ogni frame è sganciato dagli altri.

Call Stack standard

Le zone in aiuto

Zone.js permette di collegare uno o più frame a una “zona”. Nell’esempio qui sotto i frame a e c verranno eseguiti all’interno della zona AC, mentre il frame b verrà eseguito all’interno della zona B.

Nell’esempio qui sotto, viene creata la zoneAC (il nome “AC” è una semplice label), con i propri dati. I confini della zona vengono definiti chiamando Zone.run(). In questo caso zoneAB racchiuderà la funzione a(), in quanto passata come parametro in zoneAC.run(), e la funzione c(), in quanto chiamata all’interno di a(). La funzione b() è al di fuori del contesto di zoneAC.

const zoneAC = Zone.current.fork({
    name: 'AC',
    properties: {
        data: {
            value: 'initial'
        }
    }
});
function c() {
console.log(Zone.current.name); // AC
}
function b() {
    console.log(Zone.current.get('data').value); 
}
function a() {
    console.log(Zone.current.get('data').value); 
    Zone.current.get('data').value = 'updated';
    setTimeout(a, 2000);
    console.log(Zone.current.name); // AC
    c();
}
zoneAC.run(b);

Questo è utile per avere il controllo di tutto quello che succede prima, durante e dopo l’esecuzione di determinati blocchi dell’applicazione, grazie ai metodi forniti da Zone, come onZoneCreated(), beforeTask(), afterTask()

Change Detection

Quando si parla di Change Detection in Angular, si parla di aggiornamenti delle view. In altre parole il Change Detection è un meccanismo con cui Angular “capisce” quando aggiornare una view, e cosa aggiornare.

Per stabilire un terreno comune, ci rifacciamo alla definizione di view  che troviamo nei commenti sul codice sorgente di ViewRef:

A View is a fundamental building block of the application UI. It is the smallest grouping of Elements which are created and destroyed together.

È utile qui ricordare che la rappresentazione dei documenti HTML e XML è definita in una struttura ad albero chiamata Document Object Model (DOM). Questa struttura è mantenuta in memoria e contiene tutti gli elementi di una pagina, con le loro proprietà.

Esempio di DOM

Quando uno di questi componenti cambia, ad esempio i campi di un form dopo il submit, o query remote, o un timer, Angular se ne accorge e aggiorna la vista. A partire da Angular 2, il modello di CD è deterministico, ovvero inizia lo scan dalla root, e prosegue verso le foglie, sempre nella stessa direzione senza mai tornare indietro2.

Per intercettare le modifiche, Angular si serve di… Zone.js! Infatti Angular registra una propria zona, chiamata NgZone, che tramite i meccanismi visti sopra riesce ad intercettare i cambiamenti nell’interfaccia (ogni componente ha il proprio Change Detector) e a innescare l’aggiornamento, chiamando ApplicationRef.tick().

Da notare che è possibile ottimizzare ulteriormente utilizzando oggetti di tipo immutable con AngularL’immutabilità infatti significa che quando sopraggiungono modifiche, invece di aggiornare le proprietà modificate dell’oggetto, ne viene istanziato uno totalmente nuovo. Questo velocizza di molto il rendering, perché se un nodo punta ancora alla vecchia struttura, significa che non è cambiato, e di conseguenza non sono cambiati nemmeno i figli, permettendo di “saltare” interi rami durante l’aggiornamento.

Image result for angular immutable objects

Note

1: Il principio vale per tutti i linguaggi, anche se il discorso può essere più articolato quando si parla di reference, dovendo distinguere tra Stack e Heap, e per i linguaggi multi-thread, che possono avere più di un Call Stack.

2: In Angular 1 e AngularJS c’era il concetto di event loop, per cui tutti quando un qualsiasi cambiamento viene notificato tutti i componenti venivano interrogati ciclicamente, finché la UI non si “stabilizzava”. Questo poteva portare a parecchi cicli, specie nel caso in cui fossero presenti interazioni fra i componenti.

Differenza tra l’event loop di Angular 1 e il modello di Angular 2+

 

 

LINK

Do you still think that NgZone (zone.js) is required for change detection in Angular?

I reverse-engineered Zones (zone.js) and here is what I’ve found

What the hell is Zone.js and why is it in my Angular 2?

Confused about Stack and Heap?

ANGULAR CHANGE DETECTION EXPLAINED

Angular Change Detection – How Does It Really Work?

Everything you need to know about change detection in Angular

How Angular detects changes

Angular 2, 4 — Visualizing Change Detection (Default vs OnPush)

Change And Its Detection In JavaScript Frameworks