Change Detection: OnPush vs Default
To get right to the point: Angular needs to keep track of when things in your application change. Let’s say we have a class member in the class for my component:
@Component({
selector: 'app-home',
template: `
<p>{{ name }}</p>
<button (click)="changeName()">Change name</button>
`,
})
export class HomeComponent {
name = 'Josh';
changeName() {
this.name = 'Kathy';
}
}
The value for name
is initially Josh
and we are displaying that in the template. But, we have a button that triggers the changeName
method when clicked which will change this value to Kathy
. I encourage you to run this example for yourself, because we are going to play with this a bit.
If you click the button, you might be unsurprised to see that the value displayed in the template changes. This is what we would want to happen, but the underlying mechanisms for Angular to achieve this seemingly simple task aren’t so simple.
We could cause a change to the template in a different way - what about a setTimeout
that triggers the change after 2
seconds?
@Component({
selector: 'app-home',
template: ` <p>{{ name }}</p> `,
})
export class HomeComponent implements OnInit {
name = 'Josh';
ngOnInit() {
setTimeout(() => (this.name = 'Kathy'), 2000);
}
}
NOTE: We are using one of Angular’s lifecycle hooks here to run some code when the component is initialised. There are other Angular lifecycle hooks like OnDestroy
and AfterViewInit
as well.
Or, maybe we have a setInterval
changing the value every second:
@Component({
selector: 'app-home',
template: ` <p>{{ value }}</p> `,
})
export class HomeComponent implements OnInit {
value = 1;
ngOnInit() {
setInterval(() => this.value++, 1000);
}
}
If you run these examples, you will see that in every case the value is updated correctly. Angular detects when these changes occur and updates the template.
How does Angular detect changes?
We aren’t going to get into the precise underlying mechanisms that allow Angular to detect and respond to changes. This can become useful for performance optimisation, but it isn’t something we need to get into right now.
Our main goal in this lesson will be to understand the difference between the Default
change detection strategy, and the OnPush
change detection strategy. To do that, we need to understand a little bit about how the default change detection strategy works.
The key idea behind how Angular is able to achieve this is that it uses something called Zone.js
to detect when any code runs that has the potential to cause a change. The scenarios that might cause a change to your applications state include:
- Component initialisation
- Events being triggered (like our button click from before)
- Handling the response of an HTTP request
- MacroTasks such as
setTimeout()
andsetInterval()
- MicroTasks such as handling Promises
But there are also some other scenarios that could cause a change. Since Angular knows when something has happened that might cause a change, it can check your application to see if anything has changed and render the update if necessary.
With the Default
change detection strategy, Angular will check every component in the component tree. If one of these scenarios that might cause a change occurs, then Angular will check all of these components to see if the data/model they depend upon has changed. In our examples, we were only changing the HomeComponent
so the HomeComponent
is the only one that needs to be updated, but Angular still needs to check every single component regardless.
This might sound bad, and it’s certainly not optimal, but using the Default
strategy is generally fine in terms of performance. Angular can perform these checks quite quickly. Using OnPush
change detection instead will still improve performance, but as we will discuss later, it isn’t the main reason we should use it (or at least I don’t think so).
Using OnPush Change Detection
Now let’s see how the OnPush
change detection strategy differs from the default. We will revisit all of our examples from before, but this time we will update the component to use OnPush
change detection (go ahead and try all of these):
import { ChangeDetectionStrategy, Component } from '@angular/core';
@Component({
selector: 'app-home',
template: `
<p>{{ name }}</p>
<button (click)="changeName()">Change name</button>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class HomeComponent {
name = 'Josh';
changeName() {
this.name = 'Kathy';
}
}