Change Detection Strategy - faster Angular
What is it, how does Angular detect changes, render them, and which method of detecting changes will be best.
What is Change Detection in Angular?
To put it simply, Change Detection in Angular is a mechanism for detecting changes and rendering them. Angular checks the current state with the new state and if any changes have been made, the view is rerendered.
How and when it works?
As mentioned above, Angular detects and reacts to specific changes, but it does it using the built-in Zone.js library, specifically ngZone, which is a customized extension/wrapper of this library. It is responsible for managing and tracking the state of asynchronous application operations.
To each component is assigned an individual Change Detector class, which compares the state before and after changes in a given element. These changes are triggered in a specific hierarchical order in accordance with the application component tree and the Depth-first ordering technique.
For detection to work, appropriate changes or modifications must occur in our application:
Component initialization. For example, when bootstrapping an Angular application, Angular loads the bootstrap component and triggers the
ApplicationRef.tick()
to call change detection and View Rendering.Event listener. Mouse clicks, scrolling, mouseover, keyboard navigation
HTTP Data Request
Use of JavaScript timer functions such as
setTimeOut, SetInterval
Other async operations
Promise.then()
,WebSocket.onmessage()
andCanvas.toBlob()
Types of change detection.
We know that applications can be very different, small and simple or huge and complex, built by many teams, having various external libraries. With such a large variety of applications, we often need to have an individual approach to optimize them. In Angular, when it comes to detecting changes and displaying them, our task is simplified because we can implement one of two strategies that will help us improve our application. These are the settings:
Default strategy
OnPush strategy
"Default" strategy.
The Default
strategy is the standard way to detect changes and is automatically set in every Angular application (unless we change it manually). There is a pattern mentioned above, which I will try to explain better below.
For this purpose, I also prepared an application with a simple file structure, which is divided into two main sections: info-section
and user-section
, each of which has its own children, and each of them has a reusable component with a simple functionality of displaying a random number. The randomNumber
value is passed from the parent after the click event.
📂app
┣ 📂components
┃ ┗ 📂display-number
┃ ┣ 📜display-number.component.html
┃ ┗ 📜display-number.component.ts
┣ 📂info-section
┃ ┣ 📂sub-info1
┃ ┃ ┣ 📜sub-info1.component.html
┃ ┃ ┗ 📜sub-info1.component.ts
┃ ┣ 📂sub-info2
┃ ┃ ┣ 📜sub-info2.component.html
┃ ┃ ┗ 📜sub-info2.component.ts
┃ ┣ 📜info-section.component.html
┃ ┗ 📜info-section.component.ts
┣ 📂user-section
┃ ┣ 📂sub-user1
┃ ┃ ┣ 📜sub-user1.component.html
┃ ┃ ┗ 📜sub-user1.component.ts
┃ ┣ 📂sub-user2
┃ ┃ ┣ 📜sub-user2.component.html
┃ ┃ ┗ 📜sub-user2.component.ts
┃ ┣ 📜user-section.component.html
┃ ┗ 📜user-section.component.ts
┣ 📜app.component.ts
┣ 📜app.module.ts
┗ 📜main.service.ts
sub-user1.component.ts (same as sub-info1 etc.)
@Component({
selector: 'app-sub-user1',
template:
`<section>
<h2>SUB-USER-1</h2>
<button (click)="generateRandomNumber()">GENERATE</button>
<app-display-number [randomNumber]="number" />
</section>`
})
export class SubUser1Component {
number = 1;
generateRandomNumber() {
this.number = Math.floor(Math.random() * 10);
}
}
display-number.component.ts
@Component({
selector: 'app-display-number',
template: `<div>{{randomNumber}}</div>`
})
export class DisplayNumberComponent {
@Input() randomNumber: number = 1;
}
Below view of the application with an example action of clicking and changing values:
Let's analyze step by step what happens in this example.
The user clicks on a button in the
user-sub-section1
component, generates a random number and passes this value to thedisplay-number
child.The Change Detection mechanism is activated throughout the application.
Change detector, starting from the main
app-component.ts
file and descending using the Depth-first ordering method to the lowest nested component in the application, checks whether there has been a change in any values.When it encounters a component in which a value has been changed, it re-renders the value (reprinting).
Below is a diagram with a visualization of how Change Detection moves through the component tree in the same application:
As we can see above, each component ran its Change Detector, i.e. checked the values before and after changing the number. In a small application this is not so important, but as the tree of components and files to be checked grows, it may become an element worth optimizing our slowing application. This is where the OnPush
change detection strategy comes to the help.
"OnPush" strategy.
This strategy significantly speeds up application performance by triggering Change Detector only when:
The component will receive a new value from
@Input
. In this case, this value is considered immutable, which means that the reference to the transferred object must also change.An event from the component or its child will be emitted.
Observable in the component will fire an event.
We will manually run Change Detector.
Otherwise, the value checking operation will not take place in a given component and in its subtree.
To use the OnPush
strategy, add the meta data changeDetection:
ChangeDetectionStrategy.OnPush
to the component decorator:
@Component({
selector: 'app-sub-user1',
templateUrl: './sub-user1.component.html'
changeDetection: ChangeDetectionStrategy.OnPush <-------
})
To make it easier to understand how Change Detection behaves after using OnPush
, I visualized the changeDetector tree in my application. For this purpose, ChangeDetectionStrategy
has been changed to OnPush
for components marked with an asterisk info-sub-section2, user-sub-section1, user-sub-section2
.
Let's analyze what is happening one by one:
The click action on the
user-sub-section1
component has been triggered and the value passed to thedisplay-number
child .Detection of changes in the application has been started, starting from
app-component
, i.e. as in theDefault
strategy.Wherever
ChangeDetectionStrategy.OnPush
is used, changes are not detected in the component or its children, except for the component in which this value change actually occurred, i.e.user-sub-section1
.
The benefits of this, of course, include reducing the number of value comparison operations in components, which translates into smoother application operation.
When not to use the OnPush strategy?
The OnPush
strategy should not be used in components that undergo constant changes, i.e. those that:
They receive data not only from their parent via
@Input
(e.g. via service, redux, etc.).Other additional transformations of the received data are performed in the component.
They don't just display static data.
Conclusion
Change Detection Strategy is a very useful tool, and with a better understanding of when and how to use it, we can contribute to significant optimization and faster operation of our application. But first we should think about whether to use the OnPush strategy in a given component or not, so as not to create additional problems and questions why something does not work or does not change.
In the topic of Detect Changes in Angular, there are many issues worth explaining in more detail, which I hope to describe soon. Such as manual change detection with ChangeDetectorRef, the ngDoCheck hook, or the Zone.js library itself.