Today, I would like to talk about customized change detection in Angular. As developers, we strive to build applications that are snappy, interactive and alive. And reactive applications feel that way.

We can think of an Angular application as a tree of components stemming from a root component. We create each component, then arrange them to form our application. Data flows from top to bottom in the component tree. As a user interacts with our app, its state changes. The state represents any information or data that application tracks

 

Customized change detection in Angular

What triggers changes in a component’s state? User events (e.g. a click event), timers (setTimeout or setInterval), asynchronous API events like XHR and HTTP requests, and promise-based events. In order to ensure that user interface always reflects the internal state of the program, we need a mechanism to detect when component data changes, and then re-render the view as needed. Here is where change detection comes into play. It was built by Victor Savkin – Google’s former Angular team member. In his words, an Angular  application is a reactive system, with change detection being the core of it.

All components have a change detector that checks the template’s bindings. How does data binding work in Angular? The binding makes it easy to display class properties from our component and set DOM element properties to our class property values. Also, the component can listen for events from the DOM to respond accordingly for an interactive user experience. There are four basic types of binding in Angular:

TemplateType of bindingComponent
{{pageTitle}}
interpolationpagetTitle: string = ‘Product list’;
<img [style.margin.px]=’imageMargin’ />property bindingimageMargin: number = 2;
(click)=’toggleImage()’>Show Image</buttonevent bindingtoggleImage(): void { this.showImage = ! this.showImage}
<input type=’text’ [(ngModel)]=’name’two-way bindingname: string;

 

The default strategy of change detection in Angular starts at the top at its root component and goes down the component tree, checking every component even if it has not changed. It compares the current value of the property used in the template expression with the previous value of that property.

In JavaScript, primitive types are a string, number, boolean, undefined, null (and symbol in ES6), and are all passed by value.


let x = 1
let y = x 

x = 2 

console.log(x, y); // -> 2,1

Any changes in primitive type’s property will cause Angular fire the change detector and update the DOM.

In JS, there are three data types that are passed by reference: array, function, and object. That is to say that if we assign an object to a variable, that variable points to the object’s location in memory.


let x = { value: 1 };
let y = x;

x.value = 2;

console.log(x, y); // -> { value: 2 }, { value: 2 };

Objects and arrays are mutable that is they can be changed. A mutable object is an object whose state can be modified after it is created. On the other hand, string and numbers are immutable and their state cannot be changed once they are created.

Let’s look at the following sidenav component:


@Component({
    selector: 'sidenav',
    templateUrl: './sidenav.component.html',
    styleUrls: ['./sidenav.component.scss'],
})

export class SidenavComponent {
    @Input() project: IProject;
}

export interface IProject {
  id: string;
  name: string;
  keywords?: string[];
}

This component depends on a ‘project’ object (Input property). But that object is mutable and can be changed by any other component or service. That is why Angular, by default, walks the whole tree and checks the sidenav component every time it performs change detection. However, this can cause a performance issue because the framework has to do it for every component in the component tree. In a nutshell, if we work with mutable data, tracking changes is hard and negatively impacts performance in large-scale applications.

 

Customizing Change Detection in Angular

On the assumption that we use immutable objects and/or our application architecture relies on observables, we can optimize our view performance by setting the change detection strategy to OnPush on specific components. 

 

Immutables and OnPush

By using OnPush strategy with immutable objects we tell Angular that a component and its subtree depends solely on inputs and only needs to be checked if it receives a new input reference as opposed to just mutate, or if the component or its children trigger a DOM event.

To implement the strategy we simply add


changeDetection: ChangeDetectionStrategy.OnPush

in the component’s decorator.

Here we have two components: a product-list (parent) and a product-detail (child).

 

product-list.component.ts


import { Component, ChangeDetectionStrategy } from ‘@angular/core’;

@Component({
   selector: ‘product-list’,
   template: `
     <product-detail *ngFor="let product of products" [product]="product">
     </product-detail>

     <button (click)=‘changeDescription()’>Change Product Description</button>
   `
})

export class ProductListComponent {

products: Array<any> = [
 {
   "productId": 1,
   "productName": "Whiteboard Sit-Stand Desk",
   "description": "Get to stand when working and save your back.",
   "price": 495,
   "starRating": 4.7,
 },
 {
   "productId": 2,
   "productName": "Logitech, MX MASTER 2S",
   "description": "Great grit and the ability to be set up to 3 devices via Bluetooth.",
   "price": 25,
   "starRating": 4.4,
 },
 {
   "productId": 5,
   "productName": "Jump Rope",
   "description": "Perfect for a tech health enthusiast.",
   "price": 79,
   "starRating": 4.4,
 }
];

 changeDescription() {
   this.products[2].description = ‘Perfect for breaks’;
 };
}

 

product-detail.component.ts


import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
@Component({
    selector: 'product-detail',
    changeDetection: ChangeDetectionStrategy.OnPush,
    template: `
      <ul>
        <li>{{product.productName}}</li>
        <p>{{product.description}}</p>
      </ul>
    `
})

export class ProductDetailComponent {
  @Input() product;
}

If we mark the product-detail component with OnPush (see below), and won’t provide Angular with a reference to a new object but instead keep mutating the existing one, the OnPush change detector won’t get triggered. As a result, the template will not be updated.


@Component({
    selector: 'product-detail',
    templateUrl: './product-detail.component.html',
    changeDetection: ChangeDetectionStrategy.OnPush,
  })

By passing a new input object reference, the change detection will be triggered and the DOM will update as expected.


this.products[2] = {
      ...this.products[2], 
      description: 'Perfect for breaks'
}

 

Observables and OnPush

If input properties are observable, then this component can change if and only if one of its input properties emits an event.

In the following example, the product-list component has an input property products which is an Observable. The component is marked with OnPush so the change detection strategy is not triggered automatically but only if the number is a multiple of 5 or when the observable completes. We have imported ChangeDetectorRef service and injected it. On component’s initialization (in the ngOnInit life cycle hook), we subscribe to the observable for a new value. The subscribe method takes three callbacks as arguments: onNext, onError and onCompleted. OnNext callback will print out the value we got, then increment the counter. If the current counter value is a multiple of 5, we call the change detector’s markForCheck method manually. Here we tell Angular that a change has been made, so the change detector will run. Then we’re using null for the onError callback, indicating we don’t want to handle this scenario. Finally, we also trigger the change detector for the onComplete callback, so the final counter is displayed.

 

product-list.component.ts


import { Component, OnInit, Input, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
import { Observable } from 'rxjs/Rx';

@Component({
    selector: 'product-list',
    changeDetection: ChangeDetectionStrategy.OnPush,
    template: `
      <div>
        <div>Total products: {{counter}}</div>
      </div>
    `
  })
  
export class ProductListComponent {
    @Input() products$: Observable<number>;
    counter = 0;

constructor(private changeDetector: ChangeDetectorRef) {}

ngOnInit() {
  this.products$.subscribe((value) => {
      console.log('printing the value', value);
      this.counter++;
      if (this.counter % 5 === 0) {
        this.changeDetector.markForCheck();
      }
  },
  null,
  () => {
    this.changeDetector.markForCheck();
  });
  }
  
}

In the app component we create the Observable we’re passing to the product list component as input. We pass two parameters to the timer method: the first is the number of milliseconds we want to wait before producing the first value and the second is number of milliseconds we wait between values. So this observable will generate sequential values every 100 values, endlessly. Since we don’t want the observable to run forever, we use the take method, to only take the first 101 values.

 

app.component.ts


import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Rx';

@Component({
    selector: 'app-root',
    template: `
      <product-list
        [products]="productObservable">
      </product-list>
    `
    })
  
export class AppComponent implements OnInit {
  productObservable$: Observable<number>;

  ngOnInit() {
    this.productObservable$ = Observable.timer(100, 100).take(101);
  }

}

Source code for the observables comes from ng-book, the complete book on Angular 5.

When we run this code, we’ll see that the counter will only be updated for each 5 values obtained from the observer and also a final value of 101 when the observable completes.

If your application works with immutable objects or observables, you can take advantage of them and customize your change detection in Angular. OnPush will work predictably with all sorts of component designs – components that receive data directly as inputs or that have observable inputs, etc. It can make a real-world difference in a complex application with a large number of components with thousands of potential expressions to be checked.

 

If you are interested in a change detection in Angular or in software development in general, I highly recommend you to subscribe to our monthly newsletter

 

If you found this article about change detection in Angular interesting, you might like…

Webpack modules

CSS Grid solution to the problems of Float and Flexbox 

Scala generics I: Scala type bounds

Scala generics II: covariance and contravariance  

Scala Generics III: generalized type constraints

BDD: user interface testing

F-bound over a generic type in Scala

Microservices vs Monolithic architecture

“Almost-infinit” scalability