Change detection in Angular

Tavish Aggarwal

I
t is important to know how change detection works in Angular 2 to write optimized apps. In this post, I am going to explain what change detection is and how it works in angular 2. Let's get started.

What exactly is change detection?

Let's understand it with help of an example. Suppose we have a component and a view. Whenever there is any change in component (maybe because of HTTP request or any other factor) that change has to reflect in view as well. It happens because of change detection.

And vice versa whenever there is a change in view, maybe because of input box that has to reflect in component too. It also happens because of change detection.

Example of change detection

// app.component.ts
class AppComponent {

   data : string;
   myMsg : string;

   constructor(private http: HttpClient) {}

   function updateData(response) {
     this.http.get('https://localhost:3002/api/getData')
                        .subscribe(data => { this.data = data; });
   }
}


// app.component.html
<button (click)="open("Change detection occur")">Click me</button>
<p>{{data}}</p>
<p>{[myMsg}}</p>
<input [(ngModel)] ="myMsg" type="text"/>


Now we know what change detection is. Let's move forward and understand how change detection works in Angular 2.

Simple and one line answer is because of zone.js. Let's dig further and understand what is the role of zone.js in change detection.

Zone.js

A zone is responsible for:

  • Persisting the zone across async task execution.
  • Exposing visibility to the task scheduling and processing of host environment (Scoped to the current zone)

Zones are useful for debugging, testing, profiling.

Let's say we have an asynchronous function i.e. setTimeOut which runs in the different scope (web APIs) and then with the help of event loop it gets triggered to call stack after execution. During this execution, some or the other way asynchronous functions are not in the same global scope. But wait... zone.js is the solution. Consider the example shown below:

zone.fork().run(function() {

   zone.inZone = true;

    setTimeout ( function () {
         console.log("Within set time out " + zone.inZone);
     }, 1000);
});

console.log(zone.inZone);

//Output

false
Within set time out true

We can see in the code above that we have forked zone.js and added run function. All the executions that are happening inside run function (either synchronous or asynchronous) are in the zone. And the executions that are happening outside of the zone are not in the scope because of obvious reasons.

From this, we can understand that in angular core files all the events that are written outside of the zone there will be no change detection happening over there.

What are the events that notify zone.js and change detection is triggered in our application? These are as given below:

  1. Set timeout, setInterval
  2. XHR calls - like HTTP requests
  3. click, mouseover, mousedown etc

But zone.js is not your best friend to use a developer because it uses monkey patching. In case if you are not sure what monkey patching is please refer below:

Monkey Patching

Suppose if we have a class with some properties and functions. When we change properties and functions at the runtime it is known as monkey patching.

The issue with zone.js is that it intercepts some of the events like set timeout, event listener and lot more. Because of which our app performance can degrade if we don't want to intercept such events.

So it is very important to use zone.js wisely in our angular app to avoid performance issues. 

Don't worry angular service NgZone has already taken care of it. 

NgZone in Angular

NgZone is basically a forked zone that extends its API and adds some additional functionality to its execution context. Some of the events it adds are as shown below:

  • onTurnStart() - Notifies subscribers just before Angular’s event turn starts. 
  • onTurnDone() - Notifies subscribers immediately after Angular’s zone is done processing the current turn and any micro tasks scheduled for that turn.
  • onEventDone() - Notifies subscribers immediately after the final onTurnDone() callback before ending VM event. Useful for testing to validate application state.

We can extend and add new features to ngZone based on our requirement. Let's understand the runOutsideAngular function that ngZone provides.

runOutsideAngular

Sometimes we don't need to make changes in UI or we don't want to trigger change detection. Then NgZone provides a method known as runOutsideAngular to handle such scenarios.

If we are using a runOutsideAngular function, then it doesn't mean angular won't be knowing about the changes. It will only provide functionality to not update changes in UI.

Example of runOutsideAngular:

// app.componenet.ts
this.name = "Angular change detection";

outsideAngular() {
  this._ngZone.runOutsideAngular(() => {
      this.name = "Changes not reflected in UI";
   });
 }

//app.componenet.html
{{name}}
<button (click)="outsideAngular()"></button>

In the code above on click of the button, you will notice that there will be no change in UI. But this.name property will get updated.

Change detection facts in angular

  1. Change detection always happens from root component to deep node
  2. There is no global digest cycle like we have in angular 1 because of obvious reasons.
  3. We can use different change detection strategies for each and every component in our app because we don't have any global cycle.

Ways to detect changes in Angular app

There are three ways in angular to control change detection. These are:

  1. Immutable object
  2. Using change detection strategy
  3. ChangeDetectorRef

Let see and understand the above strategies with the help of an example:

Immutable object 

As we all know that string, integer etc are immutable by default. But, we have arrays and objects which are not immutable by default. There is a third party javascript that we can use to create objects immutable.

npm install --save immutable

After installing it import it in the component where needed as shown below:

import * as Immutable from 'immutable';

Now we can create immutable objects as shown below:

student = Immutable.Map({
    firstName: 'Tavish',
    lastName: 'Aggarwal'
})

So now by creating the immutable object, we cannot update objects property values. Instead, whenever we want to update the object we need to create a new instance of the object and then we can update value. Refer the code shown below:

this.student = this.student.merge({ firstName: 'Grace', lastName: 'Michael' });

Now only way to update <code>student</code> object is as shown above. 

The advantage of using the immutable object is that change detection won't run every time. Instead, now it will be run specifically when we want.

Using change detection strategy

There is two type of change detection strategy in angular. These are:

  • OnPush means that the change detector's mode will be set to check once.
  • Default means that the change detector's mode will be set to check always.

If we don't specify any change detection strategy then angular will take Default one. But to use OnPush change detection strategy we need to specify it as shown below:

@Component({
  selector: 'my-app',
  template: ``,
  changeDetection: ChangeDetectionStrategy.OnPush
})

export class AppComponent {
}

This will inform Angular that our component only depends on its inputs and that any object that is passed to it should be considered immutable. 

By adding OnPush Change detection strategy we are explicitly saying to angular that our AppComponent only changes when input properties changes. If there is no change in input property then there is no need to run change detection on AppComponent.

ChangeDetectorRef

It allows us to attach or remove change detection from the component. To have more control over OnPush change detection strategy we need ChnageDetectorRef.

Let me consider an example: Suppose if there are no updates in Input property and we are using OnPush change detection strategy then also may be because of some reason we need to detect changes. How we will do it?

The answer is ChangeDetectorRef. Refer the code shown below:

@Component({
 ...
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent {
  @Input() data: string;

  constructor(private _cd: ChangeDetectorRef) {}

  refreshContent() {
    this._cd.detectChanges();
  }
}

In the code shown above on calling <code>refreshContent</code> function, change detection will run on <code>AppComponent</code>.

I hope you like the post. If you have any questions, please leave in the comment section. I will be posting more posts on angular.

Author Info

Tavish Aggarwal

Website: http://tavishaggarwal.com

Tavish Aggarwal is a front-end Developer working in a Hyderabad. He is very passionate about technology and loves to work in a team.

Category