Angular Myth: Change Detection Strategies
Not so long ago I noticed that the change detection subject in Angular is very mythical. For the first sight, it looks very simple, and it is.
Docs
So, we have two change detection strategies: Default
and OnPush
. If you have not specified which one you want to use in your component, it’s going to use Default
one.
Our experiment we should start with the docs.
As you can see, the documentation is not that easy to base here only on the docs. Even if you follow the Change detection usage links, it’s not enough. In this case, it’s good to try using external sources to learn how it works, and we all know that it’s not the best source of truth.
Testing app
I’ve created for you an app that will let you try everything yourself. You can find it here: github.com/galczo5/experiment-change-detection-strategy. The app is hosted below in the iframe, so it’s fully interactive, and you can do every test I did.
It’s a very simple app.
Basically there are two branches of almost identical components, one branch with Default
strategy and the second one with the OnPush
.
Every branch contains three components: Parent and two children.
Every component of my app implements OnInit
, OnChanges
and DoCheck
hooks to log what is called by Angular.
Today we are not using it to do anything. I just want to know which component was checked etc.
|
|
In addition, I’ve added a button to disable/enable NgZones. NgZones is not a part of change detection strategies, but it’s strongly connected, and I decided that it’s worth showing it.
Facts
Fact 1. Default
strategy checks a lot more than OnPush
It’s easy to test it. Just click on set value
button or follow the instructions bellow:
- Click on the button “set value” in Default Strategy parent
- Value should be displayed below Child 1 in Default Strategy
- Click on the button “set value” in OnPush Strategy parent
- Observe value not being displayed below Child 1 in OnPush Strategy
Repeat the test clicking “set new value”.
This button is executing very simple code:
|
|
In case of Default
strategy you’ll see that value is rendered.
When you repeat that test in OnPush
branch value will not be rendered.
In both cases in the console, you should get a similar output.
Default:
|
|
OnPush:
|
|
So if you keep your object immutable, it should work like in method setNewValue
.
In both strategies value will be rendered.
This fact shows us why OnPush
strategy is considered
to be better for performance. Comparing values recursively is not easy and complex.
Not doing it is great.
Fact 2. Using OnPush
reduce the number of components to check
The test is even simpler than the previous one.
- Open the dev tools of your browser.
- Clear the console.
- Click on
console.log
button fromAppComponent
.
On the output you’ll see:
|
|
Here we see
that Angular is executing DoCheck
hook for every component with the Default
strategy and only for the direct child with OnPush
strategy.
This button is not changing anything in my view.
There is only a side effect in form of console.log.
In my opinion, there is no reason to check any of the components and Angular will execute some code.
When you use OnPush
it executes less code.
When you turn off the NgZone
, it will print only the console.log and no checks will be executed.
Sounds like optimization, right?
Fact 3. Change detection is strongly connected to NgZones
This test I’m starting with pushing the toggle ngzone
button. The header Noop Zone Provided
should appear.
From this point, I’m repeating the test from the first fact.
In every case, no matter if I click on set value
or set new value
there is nothing.
Value is not rendered.
The console is clear.
It looks like the app is broken.
To fix that issue, we have to execute change detection manually.
In our case we can use detect changes
button.
After that, it’s working again!
Now, you have full control of the change detection in Angular. When you work with performance, it’s very important to have it, because you don’t need to care about external bottlenecks.
Important! Check out the output in the console.
In this case, it’s executing a lot less DoCheck
hooks.
For button in Default
branch it’ll execute only the hooks for components from Default
subtree.
Analogical for the OnPush
strategy.
This test proves one very important thing.
NgZone
is the mechanism responsible for triggering the change detection cycle.
No matter if you use Default
or OnPush
, at the end value was rendered because zone.js
reacted to click event.
Fact 4. Only events from inside the app are triggering change detection
Click on the button from section in the red box bellow apps.
Nothing will happen. No hook was executed in this test.
Code outside your app will not affect your performance or trigger the change detection, and it’s great.
Summary
I’m not sure why there are a lot of myths about this subject. I heard just too many very strange answers for a question “what is the difference between change detection strategies in Angular”.
At the end, it’s pretty simple:
Default
- check recursivelyOnPush
- check only the reference to an object.
Change detection cycle is started by click event, or anything else caught by NgZone
.
In my opinion, If you care about performance,
you may want to use OnPush
strategy in every component, and you may consider switching to zone-less approach.
It’s just faster when your app is not executing code that is not necessary to render the value in one place.
I think that I proved it in this article.