Angular 2如何改变检测工作?

sup*_*ary 30 angularjs angular

在Angular 1中,更改检测是通过脏检查$ scope层次结构.我们将在模板,控制器或组件中隐式或明确地创建观察者.

在Angular 2中,我们不再有$ scope,但我们覆盖了setInterval,setTimeout等.我可以看看Angular如何使用它来触发$ digest,但是Angular如何确定改变了什么,特别是考虑到Object.observe从未进入浏览器?

这是一个简单的例子.服务中定义的对象在setInterval中更新.每个间隔重新编译DOM.

Angular如何能够告诉AppComponent正在观看该服务,以及该服务属性的值是否已经改变?

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])
});
Run Code Online (Sandbox Code Playgroud)

Mar*_*cok 36

Angular为每个组件创建一个更改检测器对象(请参阅ChangeDetectorRef),该对象跟踪每个模板绑定的最后一个值,例如{{service.a}}.默认情况下,在每个异步浏览器事件(例如来自服务器的响应,或单击事件或超时事件)之后,执行角度更改检测并使用这些更改检测器对象脏检查每个绑定.

如果检测到更改,则传播更改.例如,

  • 如果输入属性值已更改,则新值将传播到组件的input属性.
  • 如果{{}}绑定值发生更改,则新值将传播到DOM属性textContent.
  • 如果x样式,属性或类绑定中的更改值(即[style.x][attr.x][class.x]- )将新值传播到DOM以更新样式,HTML属性或类.

Angular使用Zone.js创建自己的区域(NgZone),它可以修补所有异步事件(浏览器DOM事件,超时,AJAX/XHR).这是变更检测能够在每个异步事件之后自动运行的方式.即,在每个异步事件处理程序(函数)完成之后,将执行角度变化检测.

我在这个答案中有更多的细节和参考链接: 什么是Angular2等同于AngularJS $手表?


Sas*_*sxa 18

Zone.js

变化是对事物的反应而发生的,因此在这方面它们是异步的.它们是由异步操作引起的,而在浏览器世界中则是事件.为了拦截这些事件,angular使用了zone.js,它修补了JavaScript调用堆栈(我相信,如果我错了,有人会纠正我)并暴露可用于执行其他操作的挂钩.

function angular() {...}
zone.run(angular);
Run Code Online (Sandbox Code Playgroud)

如果你想象这个angular函数是整个Angular,那就是它在区域中的运行方式.通过这样做可以拦截事件,如果它们被触发,我们可以假设发生变化,并倾听/观察它们.

ApplicationRef

实际上ApplicationRef创建了区域:

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

NgZone使用少量事件发射器创建和类:

  this._onTurnStartEvents = new EventEmitter(false);
  this._onTurnDoneEvents = new EventEmitter(false);
  this._onEventDoneEvents = new EventEmitter(false);
  this._onErrorEvents = new EventEmitter(false);
Run Code Online (Sandbox Code Playgroud)

它通过吸气剂暴露给外界:

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

ApplicationRef创建其订阅该区域的活动,特别是onTurnDone():

this.zone.onTurnDone
  .subscribe(() => this.zone.run(() => this.tick());
Run Code Online (Sandbox Code Playgroud)

变化

当事件被触发时tick(),运行函数,循环遍历每个组件:

  this._changeDetectorRefs.forEach((detector) => detector.detectChanges());
Run Code Online (Sandbox Code Playgroud)

并根据组件检测更改ChangeDetectionStrategy.这些更改将作为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;
}
Run Code Online (Sandbox Code Playgroud)

巫婆通过onChanges界面可用于我们:

export interface OnChanges { 
  ngOnChanges(changes: {[key: string]: SimpleChange}); 
}
Run Code Online (Sandbox Code Playgroud)

  • "收集了这些变化" - 我认为值得注意的是,只有输入属性更改才会通过`ngOnChanges()`收集并传递给我们.其他更改(例如,模板绑定,如`{{}}`)不包括在内.并且因为通过比较值来检测更改,所以如果input属性是JavaScript引用类型(例如,数组,对象等),则仅在引用(值)改变时通知我们.如果数组引用没有改变,但是,例如,数组的项确实改变了,那么`ngOnChanges()`不会通知我们.(我之所以提到这一点,是因为这种情况在SO上出现了几次.) (2认同)