1

I still do not understand what is the difference between cdr.detectChanges() and cdr.markForCheck() for OnPush change detection strategy from the usage view.

Eventhough I have read this SO question and InDepth explanation.

Why can't I just call cdr.detectChanges()? Why do I need to mark the tree from current component to the root (Or is it due to postpone to the next detection cycle)?

Or is it somehow required to update parent components too?

In the following example, both ways works:

import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-test',
  template: `{{ i }}`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class TestComponent implements OnInit {
  private i = 0;

  constructor(private cdr: ChangeDetectorRef) { }

  ngOnInit() {
    setInterval(() => {
      this.i++;
      // this.cdr.detectChanges(); // this works too and updates view
      this.cdr.markForCheck();     // but this is for some reason recommended
    }, 1000);
  }
}
Felix
  • 3,999
  • 3
  • 42
  • 66

2 Answers2

3

detectChanges runs a change detection cycle directly for the component
markForCheck does what it's called like, it just mark the component for check

In your example you run setInterval, that means that the changeDetection gets triggered by setInterval, but runs in your component in the next cycle.

Thats because NgZone does something like monkey patch all default api's it can reaches to run changeDetection. With on Push you hook out there and tell your component that you tell angular when it have to run additional checks.

If you want to get some deeper knowledge about NgZone and ChangeDetection take a look at this video Angular Performance: Your App at the Speed of Light. This contains very good understandable knowledge round about ChangeDetection and how NgZone works which every Angular Developer should know about.

JohnnyDevNull
  • 951
  • 1
  • 8
  • 20
  • I agree, but this is not answer to my question. What I am asking for is why is it recommended to use `markForCheck()` for `ChangeDetectionStrategy.OnPush`. `detectChanges()` works too (in both ways NgZone monkey patches async functions, but it is opted out with OnPush in the example). Why is it recommended to `markForCheck`? Is it because is it required to check the parent components too? Or is it due to postponing to the next detection cycle (still in the context with OnPush strategy)? – Felix Mar 16 '20 at 14:24
  • Well i think it is recommended because of that many actions invokes a change detection so hooking in with markForCheck would be enough instead of triggering a cycle directly, but it is as it often is that it depends on your use case. When you triggere a lot of side effects where no async pipe or @Input() is involved you would have to trigger the detectChanges() function to update the dependend components manually. – JohnnyDevNull Mar 18 '20 at 07:54
  • Here there is a good explanation: https://indepth.dev/everything-you-need-to-know-about-change-detection-in-angular/#markforcheck, and https://indepth.dev/everything-you-need-to-know-about-change-detection-in-angular/#detectchanges – JohnnyDevNull Mar 18 '20 at 10:50
1

Not 100% sure about this, but from what I understand using detectChanges() everywhere would likely be less performant than using markForCheck().

Using detectChanges()

Imagine TestComponent has several fields foo, bar, baz, and the setter for each field looks like:

set foo(value) {
  ...
  this.cdr.detectChanges();
};

Some asynchronous event could do:

testComponent.foo = ...; // triggers 1st change detection cycle on TestComponent and its children
testComponent.bar = ...; // triggers 2nd change detection cycle on TestComponent and its children
testComponent.baz = ...; // triggers 3rd change detection cycle on TestComponent and its children

// after the async event, Angular runs regular change detection
// starting from the root component,
// TestComponent was not marked for check so it is skipped.

In total, we've run change detection on TestComponent 3 times, but it's unnecessary work and we could have just done a single change detection cycle after all three changes.

Note: you could rewrite your code and restrict TestComponent's public API to optimize detectChanges() use, but that's all on you to manually figure out.

Using markForCheck()

Now let's say the setters use markForCheck():

set foo(value) {
  ...
  this.cdr.markForCheck();
};

Some asynchronous event could do:

testComponent.foo = ...; // no change detection triggered
testComponent.bar = ...; // no change detection triggered
testComponent.baz = ...; // no change detection triggered

// after the async event, Angular runs regular change detection
// starting from the root component,
// TestComponent was marked for check so change detection runs on it.

In total, change detection only runs once for TestComponent. The more expensive it is to run change detection on TestComponent (e.g. what if TestComponent has a huge subtree of child components), the more this matters.

Ray J
  • 805
  • 1
  • 9
  • 13