Check out myAngular article series with live demos and Facebook group Angular - Advanced Topics

Change Detection in Angular

Torgeir "Tor" Helgevold
- JavaScript Developer and Blogger
Published: Sun Jan 17 2016

One of the most common criticisms of Angular 1.x is the potentially inefficient change detection algorithm tied to the digest cycle. In this post I will discuss some of the major improvements to change detection in Angular.

As most Angular developers know, Angular 1.x change detection can be very heavy handed since it can't be localized to a specific area of the application. Basically when you kick off a digest cycle all bindings in the entire application will fire. This is very undesirable in a modular architecture of independent components since changes tend to only apply to the component where the change event was triggered.

“All or nothing change detection” is still the default behavior in Angular, but the new change detection approach gives us the option to limit change detection to only areas of the application where it's needed. By specifying changeDetectionStrategy.push at the component level we are able to limit change detection to only changes that directly affect the bindings in the component. If an unrelated event occurs somewhere else in the application, the change detection algorithm is smart enough to bypass our component. The obvious benefit of this is that we can drastically reduce the number of bindings to consider during a digest cycle. This approach fits nicely with the new component architecture since it's often the case that a component doesn't depend on a sibling component.

To demonstrate how we can make use of changeDetectionStrategy.push I have created a simple comment component. I will include two instances of the component and show how changeDetectionStrategy.push differs from the default strategy in practice.

I have created two distinct components, but both inherit from a common base (shown below).

import {Comment} from './comment'; export class BaseComment{ comments = []; text:string; author:string; message:string; addComment(){ let comment = new Comment(); comment.text = this.text; comment.author = this.author; this.comments.push(comment); } lastUpdated(){ return Date().toString(); } }

As you can see I have inherited the base and specified the change detection strategy at the component level like so:

//Push Strategy import {Component,ChangeDetectionStrategy} from '@angular/core'; import {BaseComment} from './base-comment'; @Component({ templateUrl:'./components/change-detection/change-detection.html', selector:'comment-section-1', changeDetection:ChangeDetectionStrategy.OnPush }) export class CommentSection1 extends BaseComment{ constructor(){ super(); this.message = 'Using ChangeDetectionStrategy.OnPush'; } } //Default Strategy} import {Component,ChangeDetectionStrategy} from '@angular/core'; import {BaseComment} from './base-comment'; @Component({ templateUrl:'./components/change-detection/change-detection.html', selector:'comment-section-2', changeDetection:ChangeDetectionStrategy.Default }) export class CommentSection2 extends BaseComment{ constructor(){ super(); this.message = 'Using ChangeDetectionStrategy.Default'; } }

I have also included the shared template.

<div style="border: 1px solid black;padding: 5px;"> <div class="alert alert-info"> {{message}} </div> <div><strong>{{lastUpdated()}}</strong></div> <div> <button (click)="addComment()">Add Comment</button> </div> <br/> <div>Author</div> <input [(ngModel)]="author" /> <div>Text</div> <input [(ngModel)]="text" /> <br/> <br/> <table class="table"> <tr *ngFor="let comment of comments"> <td>{{comment.author}}</td> <td>{{comment.text}}</td> </tr> </table> </div>

To help illustrate the difference my template includes a function binding. I use the function binding to make it easier to see when changes are triggered. However as you will see with changeDetectionStrategy.push the function binding will not fire based on events originating outside the component. Unlike changeDetectionStrategy.default where change detection always fires – same behavior as in Angular 1.x.

To help illustrate this I have created a live demo where you can see the effects of kicking off change detection cycles and observe the effects in the two components. In the instance using push strategy you will see that only internal events will update the component. However, the instance using default strategy will update during any change detection cycle – regardless of the origin of the event. You can clearly see this in the updates to the time string inside the component. Default strategy will take updates based on any trigger in the entire application, but the push strategy component limits it to button clicks or key events originating inside the component.

As I pointed out earlier the major benefit of this is limiting the number of active bindings to consider during change detection, resulting in better performance. Most applications will probably be able to benefit from this in some form, so it's definitely something to consider when creating components.

As always my code is available on Github

If you liked this article, share it with your friends on social media:

We also have a new Facebook group about advanced Angular topics.

I invite you to follow me on twitter