In this Angular 2.0 article series, we have already discussed about different basic concepts or features of AngularJs 2.0 like data binding, directives, pipes, service, route, http modules etc. Now, in this article, we will discuss about one of the main features of Angular JS Framework i.e. called Change Detection. In case you have not had a look at the previous articles of this series, go through the links mentioned below.
Change detection is the process, which allows Angular to keep our views in synchronization with our models. Change detection has changed in a big way between the old version of Angular and the new one. In Angular 1, the framework kept a long list of watchers (one for every property, which is bound to our templates), which is required to be checked every-time a digest cycle was started. This was called dirty checking and it was the only change detection mechanism available.
Since, by default, Angular 1 implemented two way data binding, the flow of changes was pretty much chaotic, models were able to change Directives. Directives were able to change models and were able to change other Directives and models were able to change other models. In Angular, the flow of information is unidirectional, even when using ngModel to implement two way data binding, which is only syntactic sugar on top of the unidirectional flow. In this new version of the framework, our code is responsible to update the models. Angular is only responsible for reflecting those changes in the components and DOM by means of the selecting change detection strategy.
Another difference between both the versions of the framework is the way the nodes of an Application (directives or components) are checked to see, if DOM needs to be updated. Due to the nature of two-way data binding, in Angular 1; there was no guarantee that a parent node would always be checked before a child node. It was possible that a child node could change a parent node or a sibling or any other node in the tree and that in turn would trigger new updates down the chain. This made it difficult for the change detection mechanism to traverse all the nodes without falling in a circular loop with the infamous message. In Angular, changes are guaranteed to propagate unidirectionally. The change detector will traverse each node only once, which always starts from the root. This means that a parent component is always checked before its children components.
How change detection works
Let's see how change detection works with a simple example.
We are going to create a simple SportsApp to show information about one sport. This app is going to consist of only two components: the SportsComponent, which shows an information about the sports and AppComponent, which holds a reference to the sports with the buttons to perform some actions.
Our AppComponent will have three properties- the slogan of the app, the title of the sports and the sportsman name. The last two properties will be passed to the SportsComponent element referenced in the template.
In the code snippet given above, we can see that our component defines two buttons, which triggers different methods. The changeProperties will update the lead sportsman of the sports by directly changing the properties of the sports object. In contrast, the method changeObject will change the information of the sportsman by creating a completely new instance of the Sports class. The Sportsman model is pretty straightforward, it is just a class, which defines the firstName and the lastName of a sportsman.
Change Detector classes
At runtime, Angular will create special classes, which are called change detectors i.e. one for every component, which we have defined. In this case, Angular will create two classes- AppComponent and AppComponent_ChangeDetector.
The goal of the change detectors is to know which model properties are used in the template of a component have changed since the last time; the change detection process ran. In order to know this, Angular creates an instance of the appropriate change detector class and a link to the component, which it's supposed to check. In our example, because we only have one instance of the AppComponent and the SportsComponent, we will have only one instance of the AppComponent_ChangeDetector and the SportsComponent_ChangeDetector.
The code snippet given below is a conceptual model of how the AppComponent_ChangeDetector class might look.
- class AppComponent_ChangeDetector {
-
- constructor(
- public previousSlogan: string,
- public previousTitle: string,
- public previousSportsman: Actor,
- public sportsComponent: SportsComponent
- ) {}
-
- detectChanges(slogan: string, title: string, actor: Actor) {
- if (slogan !== this.previousSlogan) {
- this.previousSlogan = slogan;
- this.sportsComponent.slogan = slogan;
- }
- if (title !== this.previousTitle) {
- this.previousTitle = title;
- this.sportsComponent.title = title;
- }
- if (sportsMan !== this.previousSportsman) {
- this.previousSportsman = sportsMan;
- this.sportsComponent.sportsMan = sportsMan;
- }
- }
- }
Because in the template of our AppComponent we reference three variables (slogan, title and sportsman), our change detector will have three properties to store the "old" values of these three properties, plus a reference to the AppComponent instance that it's supposed to "watch". When the change detection process wants to know, if our AppComponent instance has changed, it will run the method detectChangespassing the current model values to compare with the old ones. If a change was detected, the component gets updated.
Change Detection Strategy - Default
- import { ChangeDetectionStrategy } from '@angular/core';
-
- @Component({
-
- changeDetection: ChangeDetectionStrategy.Default
- })
- export class SportsComponent {
-
- }
Let's see what happens when a user clicks the button Change Actor Properties when using the Default strategy.
As noted previously, the changes are triggered by the events and the propagation of changes is done in two phases- the Application phase and the change detection phase.
Phase 1 (Application)
In the first phase, the Application (our code) is responsible to update the models in response to some event. In this scenario, the properties sportsMan.firstName and sportsMan.lastName are updated.
Phase 2 (Change Detection)
Since our models are updated, Angular must update the templates, using change detection. Change detection always starts at the root component. In this case, it is the AppComponent and it checks if any of the model properties are bound to its template have changed or not, comparing the old value of each property (before the event was triggered) to the new one (after the models were updated).
Change Detection Strategy - OnPush
- @Component({
-
- changeDetection: ChangeDetectionStrategy.OnPush
- })
- export class SportsComponent {
-
- }
This will inform Angular that our component only depends on its inputs and that any object, which is passed to it should be considered immutable. This time when we click Change Properties button, nothing changes in the view.
Let's follow the logic behind it again. When the user clicks the button, the method changeProperties is called and the properties of the sportsman object gets updated.
Now, to demonstrate this concept, write down the code given below, as mentioned-
sportsman.model.ts
- export class SportsMan {
- constructor(
- public firstName: string,
- public lastName: string) { }
- }
sports.component.ts
- import { Component, Input } from '@angular/core';
- import { ChangeDetectionStrategy } from '@angular/core';
- import { SportsMan } from './sportsman.model';
-
- @Component({
- moduleId: module.id,
- styles: ['div {border: 1px solid black}'],
- selector: 'app-sports',
- templateUrl: 'sports.component.html',
- changeDetection: ChangeDetectionStrategy.OnPush
- })
-
- export class SportsComponent {
- @Input() private title: string;
- @Input() private sportsman: SportsMan;
- }
sports.component.html
- <div>
- <h3>{{ title }}</h3>
- <p>
- <label>Actor:</label>
- <span>{{sportsman.firstName}} {{sportsman.lastName}}</span>
- </p>
- </div>
app.component.ts
- import { Component } from '@angular/core';
- import { SportsMan } from './sportsman.model';
-
- @Component({
- moduleId: module.id,
- selector: 'home-page',
- templateUrl: 'app.component.html'
- })
-
- export class AppComponent {
- private slogan: string = ' King of All Sports in India';
- private title: string = 'Cricket';
- private sportsMan = new SportsMan('Don', 'Bradman');
-
- changeProperties(): void {
- this.sportsMan.firstName = 'Sachin';
- this.sportsMan.lastName = 'Tendulkar';
- }
-
- changeObject(): void {
- this.sportsMan = new SportsMan('Virat', 'Kohli');
- }
- }
app.component.html
- <div>
- <h2>Demonstration of Change Detection</h2>
- <h3>Sports App Details</h3>
- <p>{{ slogan }}</p>
- <button type="button" (click)="changeProperties()">
- Change Actor Properties
- </button>
- <button type="button" (click)="changeObject()">
- Change Actor Object
- </button>
- <app-sports [title]="title" [sportsman]="sportsMan"></app-sports>
- </div>
app.module.ts
- import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
- import { BrowserModule } from '@angular/platform-browser';
- import { ReactiveFormsModule } from "@angular/forms";
- import { HttpModule } from '@angular/http';
-
- import { AppComponent } from './src/app.component';
- import { SportsComponent } from './src/sports.component';
-
- @NgModule({
- imports: [BrowserModule, ReactiveFormsModule, HttpModule],
- declarations: [AppComponent, SportsComponent],
- bootstrap: [AppComponent]
- })
- export class AppModule { }
main.ts
- import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
-
- import { AppModule } from './app.module';
-
- const platform = platformBrowserDynamic();
- platform.bootstrapModule(AppModule);
index.html
- <!DOCTYPE html>
- <html>
- <head>
- <title>Angular2 - Change Detection </title>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1">
- <link href="../resources/style/bootstrap.css" rel="stylesheet" />
- <link href="../resources/style/style1.css" rel="stylesheet" />
- <!-- Polyfill(s) for older browsers -->
- <script src="../resources/js/jquery-2.1.1.js"></script>
- <script src="../resources/js/bootstrap.js"></script>
-
- <script src="../node_modules/core-js/client/shim.min.js"></script>
- <script src="../node_modules/zone.js/dist/zone.js"></script>
- <script src="../node_modules/reflect-metadata/Reflect.js"></script>
- <script src="../node_modules/systemjs/dist/system.src.js"></script>
- <script src="../systemjs.config.js"></script>
- <script>
- System.import('app').catch(function (err) { console.error(err); });
- </script>
- <!-- Set the base href, demo only! In your app: <base href="/"> -->
- <script>document.write('<base href="' + document.location + '" />');</script>
- </head>
- <body>
- <home-page>Loading</home-page>
- </body>
- </html>
Now, run the program and the output is given below.