这篇很棒的文章If you think ngDoCheck means your component is being checked — read this article 深入解释了错误。
此答案的内容基于 Angular 版本 2.x.x。有关最新版本 4.x.x,请参阅 this post。
互联网上没有关于变更检测的内部工作原理,所以我不得不花费大约一周的时间调试源代码,所以这个答案在细节上将是相当技术性的。
角度应用程序是views 的树(AppView 类由编译器生成的组件特定类扩展)。每个视图都有一个位于cdMode 属性中的更改检测模式。 cdMode 的默认值为ChangeDetectorStatus.CheckAlways,即cdMode = 2。
当一个变更检测循环运行时,每个父视图都会检查是否应该对子视图here进行变更检测:
detectChanges(throwOnChange: boolean): void {
const s = _scope_check(this.clazz);
if (this.cdMode === ChangeDetectorStatus.Checked ||
this.cdMode === ChangeDetectorStatus.Errored)
return;
if (this.cdMode === ChangeDetectorStatus.Destroyed) {
this.throwDestroyedError('detectChanges');
}
this.detectChangesInternal(throwOnChange); <---- performs CD on child view
其中this 指向child 视图。因此,如果 cdMode 是 ChangeDetectorStatus.Checked=1,则会因为这一行而跳过直接子代及其所有后代的更改检测。
if (this.cdMode === ChangeDetectorStatus.Checked ||
this.cdMode === ChangeDetectorStatus.Errored)
return;
changeDetection: ChangeDetectionStrategy.OnPush 所做的只是将cdMode 设置为ChangeDetectorStatus.CheckOnce = 0,因此在第一次运行更改检测后,子视图将其cdMode 设置为ChangeDetectorStatus.Checked = 1,因为this code:
if (this.cdMode === ChangeDetectorStatus.CheckOnce)
this.cdMode = ChangeDetectorStatus.Checked;
这意味着下次更改检测周期开始时,不会对子视图执行更改检测。
很少有选项可以为此类视图运行更改检测。首先是将子视图的cdMode更改为ChangeDetectorStatus.CheckOnce,这可以在ngDoCheck生命周期钩子中使用this._changeRef.markForCheck()完成:
constructor(private _changeRef: ChangeDetectorRef) { }
ngDoCheck() {
this._changeRef.markForCheck();
}
这只是将当前视图及其父视图的cdMode 更改为ChangeDetectorStatus.CheckOnce,因此下次执行更改检测时会检查当前视图。
查看完整示例 here in the sources,但这里是它的要点:
constructor(ref: ChangeDetectorRef) {
setInterval(() => {
this.numberOfTicks ++
// the following is required, otherwise the view will not be updated
this.ref.markForCheck();
^^^^^^^^^^^^^^^^^^^^^^^^
}, 1000);
}
第二个选项是在视图本身上调用detectChanges,如果cdMode 不是ChangeDetectorStatus.Checked 或ChangeDetectorStatus.Errored,则在当前视图上调用run change detection。由于 onPush angular 将 cdMode 设置为 ChangeDetectorStatus.CheckOnce,因此 angular 将运行更改检测。
所以ngDoCheck 不会覆盖更改的检测,它只是在每个更改的检测周期中调用,唯一的工作是将当前视图cdMode 设置为checkOnce,以便在下一个更改检测周期中检查它变化。有关详细信息,请参阅this answer。如果当前视图的变化检测模式为checkAlways(如果不使用onPush策略则默认设置),ngDoCheck似乎没什么用。