What's the difference between markForCheck() and detectChanges()
Asked Answered
B

5

281

What is the difference between ChangeDetectorRef.markForCheck() and ChangeDetectorRef.detectChanges()?

I only found information on SO as to the difference between NgZone.run(), but not between these two functions.

For answers with only a reference to the doc, please illustrate some practical scenarios to choose one over the other.

Breadth answered 28/12, 2016 at 14:49 Comment(0)
S
370

detectChanges() : void

Checks the change detector and its children.

It means, if there is a case where any thing inside your model (your class) has changed but it hasn't reflected the view, you might need to notify Angular to detect those changes (detect local changes) and update the view.

Possible scenarios might be :

1- The change detector is detached from the view ( see detach )

2- An update has happened but it hasn't been inside the Angular Zone, therefore, Angular doesn't know about it.

Like when a third party function has updated your model and you want to update the view after that.

 someFunctionThatIsRunByAThirdPartyCode(){
     yourModel.text = "new text";
 }

Because this code is outside of Angular's zone (probably), you most likely need to make sure to detect the changes and update the view, thus:

 myFunction(){
   someFunctionThatIsRunByAThirdPartyCode();

   // Let's detect the changes that above function made to the model which Angular is not aware of.
    this.cd.detectChanges();
 }

NOTE :

There are other ways to make above work, in other words, there are other ways to bring that change inside Angular change cycle.

** You could wrap that third party function inside a zone.run :

 myFunction(){
   this.zone.run(this.someFunctionThatIsRunByAThirdPartyCode);
 }

** You could wrap the function inside a setTimeout :

myFunction(){
   setTimeout(this.someFunctionThatIsRunByAThirdPartyCode,0);
 }

3- There are also cases where you update the model after the change detection cycle is finished, where in those cases you get this dreaded error :

"Expression has changed after it was checked";

This generally means (from Angular2 language) :

I saw an change in your model that was caused by one of my accepted ways ( events, XHR requests, setTimeout, and ... ) and then I ran my change detection to update your view and I finished it, but then there was another function in your code which updated the model again and I don't wanna run my change detection again because there is no dirty checking like AngularJS anymore :D and we should use one way data flow!

You'll definitely come across this error :P .

Couple of ways to fix it :

1- Proper way : make sure that update is inside the change detection cycle ( Angular2 updates are one way flow that happen once, do not update the model after that and move your code to a better place/time ).

2- Lazy way: run detectChanges() after that update to make angular2 happy, this is definitely not the best way, but as you asked what are the possible scenarios, this is one of them.

This way you're saying : I sincerely know you ran the change detection, but I want you to do it again because I had to update something on the fly after you finished the checking.

3- Put the code inside a setTimeout, because setTimeout is patched by zone and will run detectChanges after it's finished.


From the docs

markForCheck() : void

Marks all ChangeDetectionStrategy ancestors as to be checked.

This is mostly needed when the ChangeDetectionStrategy of your component is OnPush.

OnPush itself means, only run the change detection if any of these has happened :

1- One of the @inputs of the component has been completely replaced with a new value, or simply put, if the reference of the @Input property has changed altogether.

So if ChangeDetectionStrategy of your component is OnPush and then you have :

   var obj = {
     name:'Milad'
   };

And then you update/mutate it like :

  obj.name = "a new name";

This will not update the obj reference ,hence the change detection is not gonna run, therefore the view is not reflecting the update/mutation.

In this case you have to manually tell Angular to check and update the view (markForCheck);

So if you did this :

  obj.name = "a new name";

You need to do this:

  this.cd.markForCheck();

Rather, below would cause a change detection to run :

    obj = {
      name:"a new name"
    };

Which completely replaced the previous obj with a new {};

2- An event has fired, like a click or some thing like that or any of the child components has emitted an event.

Events like :

  • Click
  • Keyup
  • Subscription events
  • etc.

So in short :

  • Use detectChanges() when you've updated the model after angular has run it's change detection, or if the update hasn't been in angular world at all.

  • Use markForCheck() if you're using OnPush and you're bypassing the ChangeDetectionStrategy by mutating some data or you've updated the model inside a setTimeout;

Stylography answered 28/12, 2016 at 14:54 Comment(11)
So if you mutate that obj , the view is not gonna be updated, and even if you run detectChanges, is not gonna work because there hasn't been any changes - this is not true. detectChanges updates view. See this in-depth explanation.Cadenza
Regarding markForCheck in the conclusion, it's not accurate, too. Here's modified example from this question, it doesn't detect object changes with OnPush and markForCheck. But the same example will work if there's no OnPush strategy.Landowska
@Maximus, Regarding your first comment, I read your post, thanks for that it was good. But in your explanation you're saying if the strategy is OnPush , means the if this.cdMode === ChangeDetectorStatus.Checked it wont update the view, that's why you'd use markForCheck.Stylography
And Regarding your links to plunker, both examples are working fine for me, I don't know what you meanStylography
@Milad, those comments came from @estus :). Mine was about detectChanges. And there is no cdMode in Angular 4.x.x. I write about that in my article. Glad you liked it. Don't forget you can recommend it on medium or follow me :)Cadenza
Yes, the examples was from me. This example doesn't work as expected when OnPush is enabled (I found that it it was commented before). title.name change isn't propagated to child component with OnPush, even with markForCheck().Landowska
@estus, of course it wont update it's child when you're updating the parent component. markForCheck will update from root to the component who called it , it doesn't go down to children, that's expected. otherwise putting one markForCheck would have made the framework to change all the tree .Stylography
@Maximus can you explain why it's not true ( your first comment ) ?Stylography
@Milad, see this reply on mediumCadenza
good explanation, but you are wrong on one point: >>>>So if you mutate that obj , the view is not gonna be updated, and even if you run detectChanges, is not gonna work because there hasn't been any changes based on the ChangeDetectionStrategy.OnPush !<<<<< I just tested it with the Peek-a-Boo example and changing it for that case. With detectChanges() the view is updated with deep changes in your model and OnPush setGarotte
Fantastically great answer, thanks! I learned several things. @StylographyOpalina
C
156

The biggest difference between the two is that detectChanges() actually triggers change detection, while markForCheck() doesn't trigger change detection.

detectChanges

This one is used to run change detection for the tree of components starting with the component that you trigger detectChanges() on. So the change detection will run for the current component and all its children. Angular holds references to the root component tree in the ApplicationRef and when any async operation happens it triggers change detection on this root component through a wrapper method tick():

@Injectable()
export class ApplicationRef_ extends ApplicationRef {
  ...
  tick(): void {
    if (this._runningTick) {
      throw new Error('ApplicationRef.tick is called recursively');
    }

    const scope = ApplicationRef_._tickScope();
    try {
      this._runningTick = true;
      this._views.forEach((view) => view.detectChanges()); <------------------

view here is the root component view. There can be many root components as I described in the What are the implications of bootstrapping multiple components.

@milad described the reasons why you potentially could need to trigger change detection manually.

markForCheck

As I said, this guy doesn't trigger change detection at all. It simply goes upwards from the current component to the root component and updates their view state to ChecksEnabled. Here is 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;
  }
}

The actual change detection for the component is not scheduled but when it will happen in the future (either as part of the current or next CD cycle) the parent component views will be checked even if they had detached change detectors. Change detectors can be detached either by using cd.detach() or by specifying OnPush change detection strategy. All native event handlers mark all parent component views for check.

This approach is often used in the ngDoCheck lifecycle hook. You can read more in the If you think ngDoCheck means your component is being checked — read this article.

See also Everything you need to know about change detection in Angular for more details.

Cadenza answered 30/7, 2017 at 5:40 Comment(16)
Why does detectChanges work on the component and its children while markForCheck on the component and ancestors?Inattentive
@pablo, that's by design. I'm not really familiar with the rationaleCadenza
@AngularInDepth.com does changedetection blocks the UI if there is very intensive processing?Pervade
@alt255, any JavaScript code that is executed in the main thread blocks UICadenza
@MaxWizardK We use OnPush change detection strategy and use ngrx. and then to display data in side the component we use selectors that get data from ngrx store... when we get the data inside the selector should we be using markForCheck or detectChanges to update the view? and why? thanksIncantation
@jerry, the recommended approach is to use async pipe, which internally tracks subscription and on every new value triggers markForCheck. So if you're not using async pipe, that's probably what you should use. However, keep in mind, that the store update should happen as a result of some async event for change detection to start. That's most always the case. But there are exceptions blog.angularindepth.com/…Cadenza
@MaxKoretskyiakaWizard thanks for the reply. Yes the store update is mostly result of a fetch or setting isFetching before . and after fetch.. but we can't always use async pipe since inside the subscribe we usually have a few things to do like call setFromValues do some comparison.. and if async itself calls markForCheck whats the problem if we call it ourselves? but again we usually have 2-3 or sometimes more selectors in ngOnInit getting different data... and we call markForCheck in all of them.. is that OK?Incantation
@jerry, you definitely can call it yourself, no problem with that, check out blog.angularindepth.com/…Cadenza
@MaxKoretskyiakaWizard thanks for the reply again. That article was a really nice read..The only problem with manual comparison is the example there was very simply and simply needed a name comparison but the real objects are much bigger and complex. How do u compare there or just run markForCheck without checking? And if I have 3-4 ngrx selectors in ngOnInit, running markForCheck in each of them, is it gonna be a performance concern? and I should be using detectChanges OR markForCheck? I have also left a comment under the article itself. Thanks that was helpfulIncantation
@jerry, I answered on medium :)Cadenza
@MaximKoretskyi, I've read in many places that change detection always starts from the root component, so even if you call it on current then it will still happen from the top of the tree. The same with marking a view to be checked - it starts from the top. Can you share your thoughts on that?Radiogram
@ŁukaszBachman, AFAIK, it will start from the current component if you use detectChanges. MarkForCheck doesn't trigger change detection.Cadenza
@MaxKoretskyi that's not true since Ivy anymore, right?Hamish
@Tim, it's still true for Ivy if you use these methods, but Ivy has different API not yet public, like markDirty, that will work differentlyCadenza
In that case, I am confused @MaxKoretskyi :( I read all your tutorials and saw your talks, I still cannot wrap my head around this: Why is the Change Detection triggered when I call markForCheck()? I understood that it marks the component and its predecessors as dirty up to the root component, and it will be checked "during the current or next CD cycle." But who triggers this next CD Cycle? Also I tried to debug into it like you did, but to no avail. Is there another way? #64158210Hamish
@Tim, what's in the call stack? There should be some async event that triggers the CD through Zone.jsCadenza
S
43

I've created a 4min screencast to explain the difference between markForCheck() and detectChanges() - https://www.youtube.com/watch?v=OcphK_aEd7I

enter image description here

Systemic answered 19/12, 2020 at 17:29 Comment(2)
When it's the best to use markForCheck?Onitaonlooker
when you are confident that CD will run after and you don't want to do extra unnecessary CDSystemic
S
32

cd.detectChanges() will run change detection immediately from the current component down through its descendants.

cd.markForCheck() will not run change detection, but mark its ancestors as needing to run change detection. Next time change detection runs anywhere, it will run also for those components which were marked.

  • If you want to reduce the number of times change detection is called use cd.markForCheck(). Often, changes affect multiple components and somewhere change detection will be called. You're essentially saying: let's just make sure this component is also updated when that happens. (The view is immediately updated in every project I've written, but not in every unit test).
  • If you can't be sure that cd.detectChanges() isn't currently running change detection, use cd.markForCheck(). detectChanges() will error in that case. This probably means you trying to edit the state of an ancestor component, which is working against the assumptions that Angular's change detection are designed around.
  • If it's critical that the view update synchronously before some other action, use detectChanges(). markForCheck() may not actually update your view in time. Unit testing something affects your view, for example, may require you manually call fixture.detectChanges() when that wasn't necessary in the app itself.
  • If you are changing the state in a component with more ancestors than descendants you may get a performance increase by using detectChanges() since you aren't unnecessarily running change detection on the component's ancestors.
Stribling answered 17/6, 2020 at 21:35 Comment(0)
D
-1

The biggest diff is markForCheck will check the binding of the current component and not call some life circle hook like DoCheck() in the child view, but the detectChange() does!

Dissolve answered 25/12, 2022 at 2:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.