Angular Change Detection Plainly

agroupp

Arthur Groupp

Posted on July 20, 2020

Angular Change Detection Plainly

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;
}
Enter fullscreen mode Exit fullscreen mode

Generate component coords. In its template set the following:

<p>X: {{coords?.x || 0}}</p>
<p>Y: {{coords?.y || 0}}</p>
Enter fullscreen mode Exit fullscreen mode

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() {
  }
}
Enter fullscreen mode Exit fullscreen mode

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() {
  }
}
Enter fullscreen mode Exit fullscreen mode

Start the application and you will see

X: 0
Y: 0
Enter fullscreen mode Exit fullscreen mode

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 };
    });
}
Enter fullscreen mode Exit fullscreen mode

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;
    });
}
Enter fullscreen mode Exit fullscreen mode

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() {
  }
}
Enter fullscreen mode Exit fullscreen mode

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 };
    });
}
Enter fullscreen mode Exit fullscreen mode

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');
  }
}
Enter fullscreen mode Exit fullscreen mode

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))
);
Enter fullscreen mode Exit fullscreen mode

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>`,
Enter fullscreen mode Exit fullscreen mode

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.

Photo by NeONBRAND on Unsplash

💖 💪 🙅 🚩
agroupp
Arthur Groupp

Posted on July 20, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Angular Change Detection Plainly
angular Angular Change Detection Plainly

July 20, 2020