How does Angular 2 change detection work?

青春壹個敷衍的年華 提交于 2019-12-17 10:27:08

问题


In Angular 1, change detection was by dirty checking the $scope hierarchy. We would implicitly or explicitly create watchers in our templates, controllers or components.

In Angular 2 we no longer have $scope, but we do override setInterval, setTimeout, et al. I can see how Angular might use this to trigger a $digest, but how does Angular determine what has changed, especially given that Object.observe never made it into browsers?

Example

Here is a simple example. An object defined in a service is updated in a setInterval. The DOM is recompiled each interval.

How is Angular able to tell that the AppComponent is watching the service, and that the value of an attribute of the service has changed?

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])
});

回答1:


Angular creates a change detector object (see ChangeDetectorRef) per component, which tracks the last value of each template binding, such as {{service.a}}. By default, after every asynchronous browser event (such as a response from a server, or a click event, or a timeout event), Angular change detection executes and dirty checks every binding using those change detector objects.

If a change is detected, the change is propagated. E.g.,

  • If an input property value changed, the new value is propagated to the component's input property.
  • If a {{}} binding value changed, the new value is propagated to DOM property textContent.
  • If the value of x changes in a style, attribute, or class binding – i.e., [style.x] or [attr.x] or [class.x] – the new value is propagated to the DOM to update the style, HTML attribute, or class.

Angular uses Zone.js to create its own zone (NgZone), which monkey-patches all asynchronous events (browser DOM events, timeouts, AJAX/XHR). This is how change detection is able to automatically run after each asynchronous event. I.e., after each asynchronous event handler (function) finishes, Angular change detection will execute.

I have a lot more detail and reference links in this answer: What is the Angular2 equivalent to an AngularJS $watch?




回答2:


Zone.js

Changes happen as a reaction to something, so in this respect they are asynchronous. They are caused by asynchronous actions, and in the browser world those are Events. To intercept those events angular uses zone.js, which patches JavaScript call stack (I beleive, someone correct me if I'm wrong) and exposes hooks that can be used to take other actions.

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

If you imagine this angular function is the entire Angular, this would be how it is run in zone. By doing so Events can be intercepted and if they are triggered we can assume changes happen, and listen/watch for them.

ApplicationRef

In reality ApplicationRef creates the zone:

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

and class NgZone is created with few event emitters:

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

that it exposes to the outside world via getters:

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

When ApplicationRef is created it subscribes to the zone's events, specifically onTurnDone():

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

Changes

When events are triggered tick() function is run which loops through every component:

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

and detects changes based on components' ChangeDetectionStrategy. Those changes are collected as an array of SimpleChange objects:

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;
}

witch is available for us through onChanges interface:

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


来源:https://stackoverflow.com/questions/35469024/how-does-angular-2-change-detection-work

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!