Come funziona Angular 2 change detection?

In Angular 1, il rilevamento delle modifiche avveniva tramite il controllo sporco della gerarchia $ scope. Creiamo implicitamente o esplicitamente osservatori nei nostri modelli, controller o componenti.

In Angular 2 non abbiamo più $ scope, ma sovrascriviamo setInterval, setTimeout, et al. Posso vedere come Angular potrebbe usare questo per triggersre un $ digest, ma come fa Angular a determinare cosa è cambiato, specialmente dato che Object.observe non è mai entrato nei browser?

Esempio

Qui c’è un semplice esempio. Un object definito in un servizio viene aggiornato in un setInterval. Il DOM viene ricompilato ogni intervallo.

In che modo Angular è in grado di dire che AppComponent sta guardando il servizio e che il valore di un attributo del servizio è cambiato?

var InjectedService = function() { var val = {a:1} setInterval(() => val.a++, 1000); return val; } var AppComponent = ng.core .Component({ selector: "app", template: ` {{service.a}} ` }) .Class({ constructor: function(service) { this.service = service; } }) AppComponent.parameters = [ new ng.core.Inject( InjectedService ) ]; document.addEventListener('DOMContentLoaded', function() { ng.platform.browser.bootstrap(AppComponent, [InjectedService]) }); 

Angolare crea un object rilevatore di modifiche (vedere ChangeDetectorRef ) per componente, che tiene traccia dell’ultimo valore di ogni associazione di template, come {{service.a}} . Per impostazione predefinita, dopo ogni evento del browser asincrono (ad esempio una risposta da un server o un evento di clic o un evento di timeout), il rilevamento delle modifiche angolari esegue e verifica in modo sporadico ogni associazione utilizzando tali oggetti del rilevatore di modifiche.

Se viene rilevata una modifica, la modifica viene propagata. Per esempio,

  • Se un valore della proprietà di input è stato modificato, il nuovo valore viene propagato alla proprietà di input del componente.
  • Se un valore di binding {{}} cambiato, il nuovo valore viene propagato alla proprietà DOM textContent .
  • Se il valore di x cambia in uno stile, attributo o associazione di classi – cioè, [style.x] o [attr.x] o [class.x] – il nuovo valore viene propagato al DOM per aggiornare lo stile, HTML attributo o class.

Angular utilizza Zone.js per creare la propria zona ( NgZone ), che esegue il patch delle scimmie su tutti gli eventi asincroni (eventi DOM del browser, timeout, AJAX / XHR). In questo modo è ansible eseguire automaticamente il rilevamento delle modifiche dopo ogni evento asincrono. Cioè, dopo che ogni gestore di eventi asincroni (funzione) termina, verrà eseguito il rilevamento del cambiamento angular.

Ho molti più dettagli e link di riferimento in questa risposta: qual è l’equivalente Angular2 di un orologio AngularJS $?

Zone.js

I cambiamenti avvengono come reazione a qualcosa, quindi sotto questo aspetto sono asincroni. Sono causati da azioni asincrone e nel mondo del browser quelli sono Eventi . Per intercettare quegli eventi angolari usa zone.js , che applica lo stack delle chiamate JavaScript (credo, qualcuno mi corregge se sbaglio) e espone degli hook che possono essere usati per compiere altre azioni.

 function angular() {...} zone.run(angular); 

Se immaginate che questa funzione angular sia l’intero Angolare, questo sarebbe come viene eseguito nella zona. In questo modo gli eventi possono essere intercettati e, se triggersti, possiamo supporre che i cambiamenti avvengano e ascoltarli / guardarli.

ApplicationRef

In realtà ApplicationRef crea la zona:

 /** * Create an Angular zone. */ export function createNgZone(): NgZone { return new NgZone({enableLongStackTrace: assertionsEnabled()}); } 

e la class NgZone viene creata con pochi emettitori di eventi :

  this._onTurnStartEvents = new EventEmitter(false); this._onTurnDoneEvents = new EventEmitter(false); this._onEventDoneEvents = new EventEmitter(false); this._onErrorEvents = new EventEmitter(false); 

che espone al mondo esterno tramite Getter:

  get onTurnStart(): /* Subject */ any { return this._onTurnStartEvents; } get onTurnDone() { return this._onTurnDoneEvents; } get onEventDone() { return this._onEventDoneEvents; } get onError() { return this._onErrorEvents; } 

Quando ApplicationRef viene creato, si abbona agli eventi della zona, in particolare su onTurnDone() :

 this.zone.onTurnDone .subscribe(() => this.zone.run(() => this.tick()); 

I cambiamenti

Quando gli eventi sono triggersti, viene eseguita la funzione tick() che esegue il ciclo di ogni componente:

  this._changeDetectorRefs.forEach((detector) => detector.detectChanges()); 

e rileva le modifiche in base al componente ” ChangeDetectionStrategy “. Tali modifiche vengono raccolte come una matrice di oggetti SimpleChange :

 addChange(changes: {[key: string]: any}, oldValue: any, newValue: any): {[key: string]: any} { if (isBlank(changes)) { changes = {}; } changes[this._currentBinding().name] = ChangeDetectionUtil.simpleChange(oldValue, newValue); return changes; } 

strega è disponibile per noi attraverso l’interfaccia onChanges :

 export interface OnChanges { ngOnChanges(changes: {[key: string]: SimpleChange}); }