Angular 零碎知识整理(四) - detectChange
detectChanges()
- The change detector is detached from the view ( see detach )
-
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);
}
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!
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.
markForCheck()
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 :
-
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 isOnPush
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 , bellow would cause a change detection to run :
obj = {
name:"a new name"
};
Which completely replaced the previous obj with a new {}
;
- 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 usingOnPush
and you're bypassing theChangeDetectionStrategy
by mutating some data or you've updated the model inside asetTimeout
;
The biggest difference between the two is that detectChanges()
actually triggers change detection, while markForCheck()
doesn't trigger change detection.