我似乎无法弄清楚为什么我需要ngDoCheck
生命周期钩子,而不是简单的通知,特别是如何在其中编写代码对于更改检测有所不同。我发现的大多数示例都显示了无用的示例,例如this one,带有一堆日志记录功能。
此外,在生成的类中,我没有看到它被用于除简单通知之外的其他内容:
conmponent / wrapper.ngfactory.js
Wrapper_AppComponent.prototype.ngDoCheck = function(view,el,throwOnChange) {
var self = this;
var changed = self._changed;
self._changed = false;
if (!throwOnChange) {
if (changed) {
jit_setBindingDebugInfoForChanges1(view.renderer,el,self._changes);
self._changes = {};
}
self.context.ngDoCheck(); <----------- this calls ngDoCheck on the component
but the result is not used
anywhere and no params are passed
}
return changed;
};
答案 0 :(得分:52)
这篇精彩的文章If you think ngDoCheck
means your component is being checked — read this article深入解释了错误。
此答案的内容基于角度版本2.x.x.对于最新版本4.x.x,请参阅this post。
互联网上没有关于变更检测的内部工作的内容,因此我不得不花费大约一周的时间来调试源代码,因此这个答案对于细节来说非常技术性。
角度应用程序是views(AppView
类的树,由编译器生成的Component特定类扩展。每个视图都有一个更改检测模式,该模式位于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
,这可以使用this._changeRef.markForCheck()
生命周期钩子中的ngDoCheck
来完成:
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
角集cdMode
到ChangeDetectorStatus.CheckOnce
,角度将运行变化检测。
因此ngDoCheck
不会覆盖已更改的检测,只需在每个更改的检测周期中调用它,并且唯一的工作就是将当前视图cdMode
设置为checkOnce
,以便在下一个更改检测周期内检查更改。有关详细信息,请参阅this answer。如果当前视图的更改检测模式为checkAlways
(如果未使用onPush策略,则默认设置),ngDoCheck
似乎没用。
答案 1 :(得分:11)
DoCheck
界面用于手动检测角度变化检测忽略的变化。可以在更改组件的ChangeDetectionStrategy
时使用,但是您知道对象的一个属性会发生变化。
检查这一项更改比让changeDetector运行整个组件更有效
let obj = {
iChange: 'hiii'
}
如果您在模板中使用obj.iChange
,则如果此值发生更改,则angular不会检测到它,因为obj
本身的引用不会更改。您需要实现ngDoCheck
来检查值是否已更改,并在组件的changeDetector上调用detectChanges
。
关于DoCheck
虽然
ngDoCheck
挂钩可以检测到英雄的名字何时发生了变化,但却有可怕的代价。无论变化发生在何处,每个变化检测周期之后,都会以极大的频率调用此挂钩。在此示例中,在用户可以执行任何操作之前,它被调用了20多次。大多数初始检查都是由Angular首次在页面上的其他位置呈现无关数据触发的。将鼠标移入另一个输入框会触发呼叫。相对较少的呼叫揭示了相关数据的实际变化。显然,我们的实现必须非常轻量级,否则用户体验将受到影响。
测试示例
@Component({
selector: 'test-do-check',
template: `
<div [innerHtml]="obj.changer"></div>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class TestDoCheckComponent implements DoCheck, OnInit {
public obj: any = {
changer: 1
};
private _oldValue: number = 1;
constructor(private _changeRef: ChangeDetectorRef){}
ngOnInit() {
setInterval(() => {
this.obj.changer += 1;
}, 1000);
}
ngDoCheck() {
if(this._oldValue !== this.obj.changer) {
this._oldValue = this.obj.changer;
//disable this line to see the counter not moving
this._changeRef.detectChanges();
}
}
}
答案 2 :(得分:0)
//需要以下内容,否则视图将不会更新
this.ref.markForCheck(); ^^^^^^^^^^^^^^^^^^^^^^^^
嗨,Maxim @ AngularInDepth.com 无需调用 this.ref.markForCheck()即可更新视图。我在consturctor和ngOnInit中测试过。 Check this
答案 3 :(得分:0)
简单来说:
通常在以下情况下是组件检查:
- 更新子组件输入绑定
- 更新DOM插值
- 更新查询列表
使用:
Deep Watch会更改错过的角度。
答案 4 :(得分:0)
注意:
用于更改检测的 angular 的默认算法通过参考比较输入绑定属性值来查找差异,了解。凉爽的。 ?
ngOnChanges() 的限制
由于角度变化检测的默认行为,ngOnChanges 无法检测是否有人更改了对象的属性或将项目推入数组?。 所以 ngDoCheck 被废弃了。
ngDoCheck() ? 哇!
检测深度变化,例如对象的属性变化或项目被推入数组,即使没有引用变化。惊人的权利?