Angular Change Detection Plainly
Tags: angular, changedetection, zonejs, typescript
Published: 2020-07-20 06:16:14

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](https://www.npmjs.com/package/zone.js?activeTab=readme) 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?](https://blog.angular-university.io/how-does-angular-2-change-detection-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.
```typescript
export interface Coordinates {
  x: number;
  y: number;
}
```

Generate component `coords`. In its template set the following:
```html
<p>X: {{coords?.x || 0}}</p>
<p>Y: {{coords?.y || 0}}</p>
```

And in the component class let’s add input binding
```typescript
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:
```typescript
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:
```typescript
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`:
```typescript
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`:
```typescript
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:
```typescript
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:
```typescript
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:
```typescript
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 
```typescript
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.


<span>Photo by <a href="https://unsplash.com/@neonbrand?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">NeONBRAND</a> on <a href="https://unsplash.com/s/photos/changes?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></span>