58

I can't seem to figure out why I need ngDoCheck lifecycle hook other than for simple notification, particularly how writing code inside of it makes a difference as regard to change detection. Most of the examples I've found show useless examples, like this one, with a bunch of logging functionality.

Also, in the generated classes I don't see it being used for something else other than simple notification:

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;
};
Poul Kruijt
  • 69,713
  • 12
  • 145
  • 149
Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488

4 Answers4

97

This great article If you think ngDoCheck means your component is being checked — read this article explains the error in depth.

The contents of this answer is based on the angular version 2.x.x. For the most recent version 4.x.x see this post.

There is nothing on the internet on the inner workings of change detection, so I had to spend about a week debugging sources, so this answer will be pretty technical on details.

An angular application is a tree of views (AppView class that is extended by the Component specific class generated by the compiler). Each view has a change detection mode that lives in cdMode property. The default value for cdMode is ChangeDetectorStatus.CheckAlways, which is cdMode = 2.

When a change detection cycle runs, each parent view checks whether it should perform change detection on the child view 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

where this points to the child view. So if cdMode is ChangeDetectorStatus.Checked=1, the change detection is skipped for the immediate child and all its descendants because of this line.

    if (this.cdMode === ChangeDetectorStatus.Checked ||
            this.cdMode === ChangeDetectorStatus.Errored)
          return;

What changeDetection: ChangeDetectionStrategy.OnPush does is simply sets cdMode to ChangeDetectorStatus.CheckOnce = 0, so after the first run of change detection the child view will have its cdMode set to ChangeDetectorStatus.Checked = 1 because of this code:

    if (this.cdMode === ChangeDetectorStatus.CheckOnce) 
         this.cdMode = ChangeDetectorStatus.Checked;

Which means that the next time a change detection cycle starts there will be no change detection performed for the child view.

There are few options how to run change detection for such view. First is to change child view's cdMode to ChangeDetectorStatus.CheckOnce, which can be done using this._changeRef.markForCheck() in ngDoCheck lifecycle hook:

      constructor(private _changeRef: ChangeDetectorRef) {   }
    
      ngDoCheck() {
        this._changeRef.markForCheck();
      }

This simply changes cdMode of the current view and its parents to ChangeDetectorStatus.CheckOnce, so next time the change detection is performed the current view is checked.

Check a full example here in the sources, but here is the gist of it:

          constructor(ref: ChangeDetectorRef) {
            setInterval(() => {
              this.numberOfTicks ++
              // the following is required, otherwise the view will not be updated
              this.ref.markForCheck();
              ^^^^^^^^^^^^^^^^^^^^^^^^
            }, 1000);
          }

The second option is call detectChanges on the view itself which will run change detection on the current view if cdMode is not ChangeDetectorStatus.Checked or ChangeDetectorStatus.Errored. Since with onPush angular sets cdMode to ChangeDetectorStatus.CheckOnce, angular will run the change detection.

So ngDoCheck doesn't override the changed detection, it's simply called on every changed detection cycle and it's only job is to set current view cdMode as checkOnce, so that during next change detection cycle it's checked for the changes. See this answer for details. If the current view's change detection mode is checkAlways (set by default if onPush strategy is not used), ngDoCheck seem to be of no use.

Aniket kale
  • 609
  • 7
  • 23
Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488
20

The DoCheck interface is used to detect changes manually which the angular change detection have overlooked. A use could be when you change the ChangeDetectionStrategy of your component, but you know that one property of an object will change.

It's more efficient to check for this one change, than to let the changeDetector run through your entire component

let obj = {
  iChange = 'hiii'
}

If you use obj.iChange inside your template, angular will not detect it if this value changes, because the reference of obj itself doesn't change. You need to implement an ngDoCheck to check if the value has changed, and call a detectChanges on your component's changeDetector.

From the angular documentation about DoCheck

While the ngDoCheck hook can detect when the hero's name has changed, it has a frightful cost. This hook is called with enormous frequency — after every change detection cycle no matter where the change occurred. It's called over twenty times in this example before the user can do anything.

Most of these initial checks are triggered by Angular's first rendering of unrelated data elsewhere on the page. Mere mousing into another input box triggers a call. Relatively few calls reveal actual changes to pertinent data. Clearly our implementation must be very lightweight or the user experience will suffer.

tested example

@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();
       }
    }
}
Poul Kruijt
  • 69,713
  • 12
  • 145
  • 149
  • thanks but I still don't understand. Your answer seems to be about `ngDoChanges`. How is it related to `ngDoCheck`? – Max Koretskyi Mar 07 '17 at 08:39
  • @Maximus what PierreDuc meant if you **don't** use `DoCheck`, you need to trigger change detection manually if the object ref doesn't change. – eko Mar 07 '17 at 08:42
  • @echonax, that's even more confusing. Maybe you can show an elaborate example? – Max Koretskyi Mar 07 '17 at 08:45
  • @Maximus my example is about `DoCheck`. `OnChanges` is triggered when the angular change detector actually detects a change. `DoCheck` is used to manually detect changes which the angular changedetector will overlook – Poul Kruijt Mar 07 '17 at 08:54
  • @PierreDuc, okay, can you please provide the `ngDoCheck` implementation for your example? – Max Koretskyi Mar 07 '17 at 08:58
  • I've added an example, although i did not test it. But i believe if you disable the `ngDoCheck` method, the value will not update – Poul Kruijt Mar 07 '17 at 09:15
  • @PierreDuc, thanks, now it's more clear. So basically I'm required to call ` this._changeRef.detectChanges();` inside the method if value changed, correct? By the way, just tried the code, the counter doesn't get updated. Maybe take a look please? – Max Koretskyi Mar 07 '17 at 10:03
  • Yes, if you manually detected a change, you need to call the changeDetector to update the component. I tried to code as well, before you replied, and it does work for me. The value updates now.. if i remove the `detectChanges` it does not – Poul Kruijt Mar 07 '17 at 10:13
  • @PierreDuc, okay, thanks. It doesn't work for me however. I'll go explore the sources to understand how it works then. But `this._changeRef.detectChanges();` is probably what was missing in my understanding. – Max Koretskyi Mar 07 '17 at 10:35
  • 1
    @PierreDuc, can you please take a look at [this plunker](https://plnkr.co/edit/WR1uwIhQzYjD8m78SWyl?p=preview)? The counter is not updated there as well – Max Koretskyi Mar 07 '17 at 11:35
  • @Maximus you need to remove `changeDetection: ChangeDetectionStrategy.OnPush` line inside the `@Component` annotation in `TestDoCheckComponent` Fixed plunker: https://plnkr.co/edit/lwy2bcCVS1CwEtGlsYfq?p=preview – eko Mar 08 '17 at 05:23
  • @echonax that will not show a use-case of `DoCheck`. It's weird though, I can simulate it fine on my own project, but yes, in the plunkr it doesn't seem to work. As far as I know there should be a `DoCheck` after the interval, regardless of the `ChangeDetectionStrategy`. Note though that I am working with the latest angular 4 version – Poul Kruijt Mar 08 '17 at 08:12
  • @PierreDuc I totally understand your point but it seems that when we use `OnPush`, it ignores the `DoCheck` changes in the template :/ (I just wanted to fix the plunker :P) – eko Mar 08 '17 at 08:15
  • @PierreDuc, sorry, but I had to revoke my acceptance from your answer since it's a bit incorrect. I've upvoted though since you pointed me in the right direction with the requirements to call a method inside `doCheck`. I've spent a week figuring out the internals and please see my findings [here](http://stackoverflow.com/a/42807309/2545680). – Max Koretskyi Mar 15 '17 at 10:49
  • @echonax, check out my correct answer [here](http://stackoverflow.com/a/42807309/2545680) – Max Koretskyi Mar 15 '17 at 10:50
  • @echonax, sure, I'll let you know once I have my article published. You can follow [me on medium](https://medium.com/@maximus.koretskyi). – Max Koretskyi Mar 15 '17 at 10:56
  • 1
    @echonax, published an article on changed detection [here](https://hackernoon.com/everything-you-need-to-know-about-change-detection-in-angular-8006c51d206f), you might want to check it out – Max Koretskyi Apr 28 '17 at 09:13
8

Note:

The default algorithm of angular for change detection looks for differences by comparing input-bound properties value by reference, understand. Cool.

Limitation of ngOnChanges()

Due to default behavior of angular change detection, ngOnChanges can't detect if someone changes a property of an object or push an item into array . So ngDoCheck comes to recuse.

ngDoCheck() wow!

Detect deep changes like a property change in object or item is pushed into array even without reference change. Amazing Right

mabdullahse
  • 3,474
  • 25
  • 23
5

In simple terms :

It's generally a component check on following occasions:

  • Update child component input bindings
  • Update DOM interpolations
  • Update query list

Use :

Deep Watch changes which angular misses out.

Saumyajit
  • 678
  • 8
  • 10