Angular Change Detection Plainly
Arthur Groupp
Posted on July 20, 2020
Change detection is one of the most exciting features of Angular framework. It gives us opportunity to work with data inside application without caring of its display. Actually, it takes values of our component class properties that were bound to template and update DOM every time the values are changed. Perfect! When I saw this for the first time, it was so amazing that I became Angular developer.
In most cases, it just works and you can be just happy with it. However, sometimes things are going wrong and you need to understand what happened.
In two words, Angular patch the browser during load with Zone.js and it gives us this functionality out of the box. There is very detailed and great written article about this mechanics "Angular Change Detection - How Does It Really Work?".
I will not repeat this article, I want to show you how the things work with the help of simple example we going to build.
Let’s go.
Initial setup
In this example, we will create the application that will detect and show us coordinates of mouse click on the screen. Create new Angular application. Create new folder data
in app
folder and create file coordinates.ts
in it. It will be the interface
representing Cartesian coordinates.
export interface Coordinates {
x: number;
y: number;
}
Generate component coords
. In its template set the following:
<p>X: {{coords?.x || 0}}</p>
<p>Y: {{coords?.y || 0}}</p>
And in the component class let’s add input binding
import { Component, OnInit, Input } from '@angular/core';
import { Coordinates } from '../data/coordinates'
@Component({
selector: 'app-coords',
templateUrl: './coords.component.html',
styleUrls: ['./coords.component.css']
})
export class CoordsComponent implements OnInit {
@Input() coords: Coordinates;
constructor() { }
ngOnInit() {
}
}
AppComponent
should be modified a bit too:
import { Component, OnInit } from '@angular/core';
import { Coordinates } from './data/coordinates';
@Component({
selector: 'my-app',
template: `<app-coords [coords]="coords"></app-coords>`,
styles: []
})
export class AppComponent implements OnInit {
coords: Coordinates = {x: 0, y: 0};
ngOnInit() {
}
}
Start the application and you will see
X: 0
Y: 0
on the screen. You can click anywhere on the screen, nothing happens.
Initial setup is done.
Default change detection
Angular provides two change detection strategies. Default and OnPush. First, let’s make things work with Default.
Default change detection strategy detects when component class property or input data change and updates DOM. It's already fires when component send event, but we don't examine it in this example.
Modify ngOnInit
of AppComponent
this way:
ngOnInit() {
window.addEventListener('click', (event: MouseEvent) => {
this.coords = { x: event.x,y: event.y };
});
}
Now, every time you click on the screen you will see the coordinates of your mouse cursor at the moment of click.
Feel free to play with it, it’s really amazing. Let’s see what’s happening. Every time you click on screen, the property of AppComponent
coords
get new object with coordinates. This property is input for CoordsComponent
coords
property. Every time you click, CoordsComponent
get new value on its input and change detection fires.
Let’s make the task for Angular more complex. Let’s keep our object and will change its property values only. In this case, CoordsComponent
input won’t change, it will be the same object. Modify ngOnInit
:
ngOnInit() {
window.addEventListener('click', (event: MouseEvent) => {
this.coords.x = event.x;
this.coords.y = event.y;
});
}
Still works! The Default change detection strategy is smart enough to make deep comparison of previous object values and new one, even if we keep the same object. This is exciting. However, every amazing thing in this world has a price. The price of this functionality is the performance. If we have many components on one page with input manipulations all the time, our application can become slow. Certainly, in our coordinate application, we can’t reproduce it, but we need to study it too.
OnPush change detection
OnPush change detection checks input values only. Let’s experiment with it. Modify CoordsComponent
:
import {
Component, OnInit,
Input, ChangeDetectionStrategy
} from '@angular/core';
import { Coordinates } from '../data/coordinates'
@Component({
selector: 'app-coords',
templateUrl: './coords.component.html',
styleUrls: ['./coords.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CoordsComponent implements OnInit {
@Input() coords: Coordinates;
constructor() { }
ngOnInit() {
}
}
Now click somewhere on the screen, nothing is working, you still have zeroes. Let’s return our first behavior of AppComponent:
ngOnInit() {
window.addEventListener('click', (event: MouseEvent) => {
this.coords = { x: event.x,y: event.y };
});
}
Click on the screen and it works! So, this is the main difference between Default and OnPush strategies.
OnChanges
Angular has very useful lifecycle hook called ngOnChanges
. It’s a method of component class that fires every time change detection occurs. In this method, you can modify your component state or incoming data every time it changes. To start using it your component class must implement OnChanges
interface:
import {
Component, OnInit, Input,
OnChanges, ChangeDetectionStrategy
} from '@angular/core';
import { Coordinates } from '../data/coordinates'
@Component({
selector: 'app-coords',
templateUrl: './coords.component.html',
styleUrls: ['./coords.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CoordsComponent implements OnInit, OnChanges {
@Input() coords: Coordinates;
constructor() { }
ngOnInit() {
}
ngOnChanges() {
console.log('Changes detected');
}
}
Angular way of doing things
Now, instead of doing things like Javascript ninjas, let’s do everything in Angular way. In AppComponent
we will create property mouseCoords$
that will be observable from mouse click event:
mouseCoords$ = fromEvent(window, 'click').pipe(
map((event: MouseEvent) => ({x: event.x, y: event.y} as Coordinates))
);
Now let’s remove old coords property and bind this one though async pipe to CoordsComponent
input
template: `<app-coords [coords]="mouseCoords$ | async"></app-coords>`,
Now, click on the screen, and everything works with OnPush performant strategy.
Conclusion
Change detection is the cornerstone of Angular framework. Understanding of it is highly necessary of being Angular developer. Lots of times I had the situation when nothing works and I have no idea why.
I hope this plainly explanation will help you to understand better what’s going on under the hood and, may be one day, to give correct answer during job interview.
Posted on July 20, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.