Change detection in Angular (Obviously 2.0 & greater)

Let me define first what change detection is, in simple terms any state change that needs to be propagated to UI and ice versa. Now the application state changes can happen in   following ways which apparently all are asynchronous in nature.

  • Events – Click, selection changed, keyup
  • Remote API Ajax Calls(XHR)
  • Timers – setTimeout(), setInterval()

Now for sure we know what how the application state change happens but there is still work of notifying Angular that state has changed and do the further magic. After all in the angular code we are still using Native API’s (like setTimeOut…) there are no angular specific API’s. So there is some thing called as Zone.js which does all the magic. You must have seen zone references whenever u see any error in the console window.

So what are zone and how do they work?

As one liner definition we can say that Zones are basically an execution context for asynchronous operations. Zones basically monkey patch these native methods and provide hooks for executing change detection.  As soon as we embed zone.js in our site, pretty much all methods that cause asynchronous operations are monkey-patched to run in a new zone. For example, when we call setTimeout() we actually call Zone.setTimeout().

So now we know that zones have hooks for asynchronous operation but how does it marry with angular, answer lies in to the ApplicationRef class and source code below, it simplified version of the code….

class ApplicationRef {

  changeDetectorRefs:ChangeDetectorRef[] = [];

  constructor(private zone: NgZone) {
    this.zone.onTurnDone
      .subscribe(() => this.zone.run(() => this.tick());
  }

  tick() {
    this.changeDetectorRefs
      .forEach((ref) => ref.detectChanges());
  }
}

ApplicationRef listens to NgZones onTurnDone event. Whenever this event is fired, it executes a tick() function which essentially performs change detection.

In Angular, each component has its own change detector. So in the component tree each component will have its own change detector. This allows us to control, for each component individually, how and when change detection is performed

ChnageDetection1

Let us assume that in the component tree an event has been triggered in the Child 2.1 component.  As mentioned earlier zones have hook in to these event handlers, zones execute the given handler and notify Angular when it is done, which eventually causes Angular to perform change detection. Now for angular change detection always flows from top to bottom. Change detection is also always performed from top to bottom for every single component, every single time, starting from the root component

ChnageDetection2ChnageDetection3ChnageDetection4

This is much more predictable change detection than in Angular JS where it used to digest loop or cycle.

CD (change detection) gets stable after a one pass, if one of our components causes any additional side effects after the first run during change detection, then angular will throw an exception.

Now that we have talked @ Change detectors in Angular but what are these Change detectors, well these are not one fit all generic classes which does the change detection for all components. Angular creates CD classes at runtime for each component, which are monomorphic, because they know exactly what the shape of the component’s model is.  Monomorphic are king of opposite of polymorphic which do not have any runt time overrides or sub types.

Angular also gives control of the change detection, if we wish to. We can tell angular to run CD only for the part of the component hierarchy tree. This can be achieved with 2 things

  • Immutable data structures
  • Observables

Lets consider optimization through Immutability first, consider the followin component

import { Component, OnInit } from '@angular/core';
import { UtilityService } from './../services/utility.service';
@Component({
  templateUrl: ''
})
export class GrandChildComponent implements OnInit {
  public greatGrandChildDetails: GreatGrandChildDetails;
  public currentDateTime : string;
  constructor(private utilService: UtilityService) {
    this.currentDateTime = this.utilService.currentDateTime.toISOString();
    this.greatGrandChildDetails = new GreatGrandChildDetails();
    this.greatGrandChildDetails.firstName = "Amol";
    this.greatGrandChildDetails.lastName = "Gote";
   }

  ngOnInit() {
  }

  changeGrandChildData(){
    this.greatGrandChildDetails.firstName = "John";
    this.greatGrandChildDetails.lastName = "Doe";
  }

}

export class GreatGrandChildDetails{
  firstName: string;
  lastName: string;
}

GrandChildComponent uses as a child component, which has an input property details. We’re passing data to that component with GrandChildComponent own greatGrandChildDetails property. greatGrandChildDetails is an object with two properties. In addition, there’s a method changeGreatGrandChildData(), which changes the first name and last name of greatGrandChildDetails, No magic going on here.
The important part is that changeGreatGrandChildData() mutates greatGrandChildDetails, by changing its first and last name property. Even though that property is going to be changed, the greatGrandChildDetails reference itself stays the same.
What happens when change detection is performed, assuming that some event causes changeGreatGrandChildData() to be executed? First, greatGrandChildDetails.firstName and greatGrandChildDetails.lastName gets changed, and then it’s passed to . ’s change detector. It now checks if the given details (greatGrandChildDetails) is still the same as before, and yes, it is (reference hasn’t changed), however, the first and last name property has changed, so Angular will perform change detection for that object nonetheless. Because objects are mutable by default in JavaScript (except for primitives), Angular has to be conservative and run change detection every single time for every component when an event happens. This is where immutable data structures come into play.

Immutable objects cannot be changed and we have to change then we have change the reference.

this.greatGrandChildDetails = Immutable.create({
              firstName: 'Amol',
              lastName: 'Gote'
            });

this.greatGrandChildDetailsNew = this.greatGrandChildDetails
                                  .set('firstName', 'John');

In the above case Immutable.create is just sample API to create Immutable, you can use any library to create immutable objects. Now when I am changing the first Name property then it is going to return a whole new reference to the object.

this.greatGrandChildDetails !=== this.greatGrandChildDetailsNew from a reference perspective both are pointing to different references. If we use immutable objects in our Angular app, all we need to do is tell Angular that a component can skip change detection, if the input has not changed, let’s look at the great-grand-child component.

Immutable-Component

We can skip entire subtrees when immutable objects are used and Angular is informed accordingly.
Immutable
ChangeDetectionStrategy.OnPush – 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.

Now let’s talk @ Observables

Observables give us some guarantees of when a change has happened, unlike immutable objects, they don’t give us new references, instead, they fire events we can subscribe to in order to react to them.

Lets look at this example

import { Component, OnInit, ChangeDetectionStrategy, Input } from '@angular/core';
import { UtilityService } from './../services/utility.service';
@Component({
  template: '{{hitCounter}}',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent implements OnInit {

  @Input() addChildItemStream:Observable;
  public hitCounter : number = 0;

  ngOnInit() {
    this.addChildItemStream.subscribe(() => {
      this.hitCounter++; 
    })
  }

}

We set the change detection strategy to OnPush, so change detection isn’t performed all the time, only when the component’s input properties change, the reference of addChildItemStream will never change, so change detection is never performed for this component’s subtree. This is a problem because the component subscribes to that stream in its ngOnInit life cycle hook and increments the counter. This is application state change and we want to have this reflected in our view. This is where ChangeDetectorRef comes to the rescue, it has an API markForCheck(), it marks the path from our component until root to be checked for the next change detection run, it simply iterates upwards and enables checks for every parent component up to the root.

import { Component, OnInit, ChangeDetectionStrategy, Input, ChangeDetectorRef } from '@angular/core';
import { UtilityService } from './../services/utility.service';
@Component({
  template: '{{hitCounter}}',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent implements OnInit {

  @Input() addChildItemStream:Observable<any>;
  public hitCounter : number = 0;
  constructor(private changeDetector: ChangeDetectorRef) {

  }

  ngOnInit() {
    this.addChildItemStream.subscribe(() => {
      this.hitCounter++; // application state changed
      this.changeDetector.markForCheck();
    })
  }

}
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s