5

I have a general Angular question:

Why does Angular async pipe use cdr.markForCheck() instead of cdr.detectChanges()?

What I see there are two main differences in these two 'styles':

  1. markForCheck() marks the path to be checked up to the root component - what to update
  2. markForCheck() lets the change detection happen in current or next cycle - timing

My thoughts or questions:

  1. why do we need to check whole path to the root (in async pipe)? Why not just current component? (detectChanges()) - this relates to what to update

  2. why just mark only (for current/next cycle - in markForCheck() using ngZone)? Why not detect changes immedially? (detectChanges()) this relates to timing

  3. what if there is no ngZone async trigger / no async operation? Then the view update won't happen?

  4. what would happen if we changed async pipe to use detectChanges() instead?


async pipe:

  private _updateLatestValue(async: any, value: Object): void {
    if (async === this._obj) {
      this._latestValue = value;
      this._ref.markForCheck();
    }
  }

Edit:

Please do not explain what each of the methods does as I read it in docs many times and it is not comprehensible for me to understant from the async point of view. The important part for me to know is why in regards to what to update and timing.

Felix
  • 3,999
  • 3
  • 42
  • 66

2 Answers2

2

For proper documentation check ChangeDetectorRef

detectChanges

Checks this view and its children

If your class has changed but it the view has not been updated yet, you need to notify Angular to detect those changes.

markForCheck

When a view uses the OnPush (checkOnce) change detection strategy, explicitly marks the view as changed so that it can be checked again.

From the text alone you can already see, that this strategy is used for cases, where the components detection strategy has been changed to onPush (e.g. if an @Input() has changed).

Update

To answer your questions directly:

  1. why do we need to check whole path to the root (in async pipe)? Why not just current component? (detectChanges()) - this relates to what to update

Its not checking the root, but rather the ancestors of your component enter image description here

  1. why just mark only (for current/next cycle - in markForCheck() using ngZone)? Why not detect changes immedially? (detectChanges()) this relates to timing

I would assume that this is a performance related topic. Gather all the checks which needs to be run and perform them in the next cycle.

  1. what if there is no ngZone async trigger / no async operation? Then the view update won't happen?

Correct, if Angular will not be notified, then nothing will change in the view

  1. what would happen if we changed async pipe to use detectChanges() instead?

I guess it would work as well, but instead of only checking the changes it would directly perform the update of the view.

Marek W
  • 699
  • 4
  • 14
  • Sorry but this answer does not answer my question - it refers to documentation and do not explain why. Please read my question again – Felix Oct 19 '20 at 09:28
  • Let me do some research before - I need to prepare an example app to demonstrate #3 question. Also I am still confused about #4 because there is no overlap in terms of what to update - up to the root (markForCheck) / current component and children (detectChanges). Why is there not only timing overlap if it is only performance issue? markForCheck - current or future check to the root vs detect changes that would detect changes now up to the root (also as markForCheck)? – Felix Oct 19 '20 at 14:38
  • I would like to add that `detectChanges()` strategy is used in *rx-angular* library `push` pipe. It schedules all the gathered *changeDetections* up until next `renderAnimationsFrame`. See here https://github.com/rx-angular/rx-angular/blob/master/libs/template/src/lib/push/push.pipe.ts - It is there mentioned also, that the tree marked up the root is not a very efficient solution (`markForCheck()`). – Felix Dec 02 '20 at 12:36
1

As Angular doc says:

detectChanges() is:

Checks this view and its children. Use in combination with detach to implement local change detection checks.

And

markForCheck() is:

When a view uses the OnPush (checkOnce) change detection strategy, explicitly marks the view as changed so that it can be checked again.

So detectChanges() runs change detection immediately, however markForCheck() does not run change detection.

So it looks like async pipe uses markForCheck to avoid performance issues. I mean using markForcheck() in async pipe helps to avoid running of unnecessary change detection.

UPDATE:

Question 1:

What if there is no current change detection in progress and there is no ngZone async trigger for future CD?

No, view will not be updated, however, it will mark all components from current component to root component that their state is CheckEnabled. The source code:

export function markParentViewsForCheck(view: ViewData) {
    let currView: ViewData|null = view;
    while (currView) {
        if (currView.def.flags & ViewFlags.OnPush) {
           currView.state |= ViewState.ChecksEnabled;
        }
    currView = currView.viewContainerParent || currView.parent;
    }
}

Question 2:

And in regards to what to update, why do I need to mark whole path up to the root? ...and in the second solution (detectChanges) not?

Please, read this nice answer Angular markForCheck vs detectChanges. In short, it is better to use markForCheck() method.

StepUp
  • 36,391
  • 15
  • 88
  • 148
  • *So it looks like...* does not convince me :) What if there is no current change detection in progress and there is no ngZone async trigger for future CD? Then the view wont be updated? And in regards to what to update, why do I need to mark whole path up to the root? ...and in the second solution (`detectChanges`) not? – Felix Oct 19 '20 at 09:41
  • 1
    I still don't understand why when we are using async pipe inside a OnPush Component, the view is updated when a new event is emitted. `markForCheck` doesn't not run change detection, so the view should be updated only after the next change detection, not immediately – Olivier Boissé Jun 30 '21 at 16:17
  • @OlivierBoissé found the answer here: https://stackoverflow.com/a/62040141/479619, in short, tick() is called after markForCheck which results in change detection running. – cbros2008 Jul 16 '21 at 16:30
  • @cbros2008 the angular documentation should explain this behavior because you need to go deep into the code to find how its really works – Olivier Boissé Jul 16 '21 at 17:01