为什么我们需要`ngDoCheck`

时间:2017-03-07 08:21:31

标签: angular angular2-docheck

我似乎无法弄清楚为什么我需要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;
    };

5 个答案:

答案 0 :(得分:52)

这篇精彩的文章If you think ngDoCheck means your component is being checked — read this article深入解释了错误。

此答案的内容基于角度版本2.x.x.对于最新版本4.x.x,请参阅this post

互联网上没有关于变更检测的内部工作的内容,因此我不得不花费大约一周的时间来调试源代码,因此这个答案对于细节来说非常技术性。

角度应用程序是viewsAppView类的树,由编译器生成的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视图。因此,如果cdModeChangeDetectorStatus.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.CheckedChangeDetectorStatus.Errored,则会run change detection在当前视图上。由于onPush角集cdModeChangeDetectorStatus.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() ? 哇!

检测深度变化,例如对象的属性变化或项目被推入数组,即使没有引用变化。惊人的权利?