Felix
Felix

Reputation: 4595

Angular: why do I need to call cdr.markForCheck() for OnPush strategy? Why is not detectChanges() sufficient?

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);
  }
}

Upvotes: 1

Views: 3928

Answers (2)

Ray J
Ray J

Reputation: 845

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.

Upvotes: 1

JohnnyDevNull
JohnnyDevNull

Reputation: 982

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.

Upvotes: 3

Related Questions