ngdocheck

ngdocheck

ngDoCheck method is called when Angular runs its change detection process so that directives have an opportunity to update any state that isn’t directly associated with an input property.

Dealing with collection-level data changes using ngdocheck

The second type of change occurs when the objects within the collection are added, removed, or replaced. Angular doesn’t detect this kind of change automatically, which means the iterating directive’s ngOnChanges method won’t be invoked.

Receiving notifications about collection-level changes is done by implementing the ngDoCheck method, which is called whenever a data change is detected in the application, regardless of where that change occurs or what kind of change it is. The ngDoCheck method allows a directive to respond to changes even when they are not automatically detected by Angular. Implementing the ngDoCheck method requires caution, however, because it represents a pitfall that can destroy the performance of a web application. To demonstrate the problem, the following example implements the ngDoCheck method so that the directive updates the content it displays when there is a change.

Implementing the ngDoCheck Methods in the iterator.directive.ts File in the src/app Folder

import { Directive, ViewContainerRef, TemplateRef,
 Input, SimpleChange } from "@angular/core";
@Directive({
 selector: "[paForOf]"
})
export class PaIteratorDirective {
 constructor(private container: ViewContainerRef,
 private template: TemplateRef<Object>) {}
 @Input("paForOf")
 dataSource: any;
 ngOnInit() {
 this.updateContent();
 }
 ngDoCheck() {
 console.log("ngDoCheck Called");
 this.updateContent();
 }
 private updateContent() {
 this.container.clear();
 for (let i = 0; i < this.dataSource.length; i++) {
 this.container.createEmbeddedView(this.template,
 new PaIteratorContext(this.dataSource[i],
 i, this.dataSource.length));
 }
 }
}
class PaIteratorContext {
 odd: boolean; even: boolean;
 first: boolean; last: boolean;
 constructor(public $implicit: any,
 public index: number, total: number ) {
 this.odd = index % 2 == 1;
 this.even = !this.odd;
 this.first = index == 0;
 this.last = index == total - 1;
 // setInterval(() => {
 // this.odd = !this.odd; this.even = !this.even;
 // this.$implicit.price++;
 // }, 2000);
 }
}

The ngOnInit and ngDoCheck methods both call a new updateContent method that clears the contents of the view container and generates new template content for each object in the data source. I have also commented out the call to the setInterval function in the PaIteratorContext class.

To understand the problem with collection-level changes and the ngDoCheck method, I need to restore the form to the component’s template, as shown in the following example.

Restoring the HTML Form in the template.html File in the src/app Folder

<style>
 input.ng-dirty.ng-invalid { border: 2px solid #ff0000 }
 input.ng-dirty.ng-valid { border: 2px solid #6bc502 }
</style>
<div class="row m-2">
 <div class="col-4">
 <form novalidate [formGroup]="form" (ngSubmit)="submitForm(form)">
 <div class="form-group" *ngFor="let control of form.productControls">
 <label>{{control.label}}</label>
 <input class="form-control"
 [(ngModel)]="newProduct[control.modelProperty]"
 name="{{control.modelProperty}}"
 formControlName="{{control.modelProperty}}" />
 <ul class="text-danger list-unstyled"
 *ngIf="(formSubmitted || control.dirty) && !control.valid">
 <li *ngFor="let error of control.getValidationMessages()">
 {{error}}
 </li>
 </ul>
 </div>
 <button class="btn btn-primary" type="submit"
 [disabled]="formSubmitted && !form.valid"
 [class.btn-secondary]="formSubmitted && !form.valid">
 Create
 </button>
 </form>
 </div>
 <div class="col-8">
 <div class="checkbox">
 <label>
 <input type="checkbox" [(ngModel)]="showTable" />
 Show Table
 </label>
 </div>
 <table *paIf="showTable"
 class="table table-sm table-bordered table-striped">
 <tr><th></th><th>Name</th><th>Category</th><th>Price</th></tr>
 <tr *paFor="let item of getProducts(); let i = index; let odd = odd;
 let even = even" [class.bg-info]="odd" [class.bg-warning]="even">
 <td>{{i + 1}}</td>
 <td>{{item.name}}</td>
<td>{{item.category}}</td>
 <td>{{item.price}}</td>
 </tr>
 </table>
 </div>
</div>

When you save the changes to the template, the HTML form will be displayed alongside the table of products, as shown in the following image.

ngdocheck
ngdocheck

The problem with the ngDoCheck method is that it is invoked every time Angular detects a change anywhere in the application—and those changes happen more often than you might expect.

To demonstrate how often changes occur, I added a call to the console.log method within the directive’s ngDoCheck method in the above example so that a message will be displayed in the browser’s JavaScript console each time the ngDoCheck method is called. Use the HTML form to create a new product and see how many messages are written out to the browser’s JavaScript console, each of which represents a change detected by Angular and which results in a call to the ngDoCheck method.

A new message is displayed each time an input element gets the focus, each time a key event is triggered, each time a validation check is performed, and so on. A quick test adding a Running Shoe product in the Running category with a price of 100 generates 27 messages on my system, although the exact number will vary based on how you navigate between elements, whether you need to correct typos, and so on.

For each of those 27 times, the structural directive destroys and re-creates its content, which means producing new tr and td elements, with new directive and binding objects.

There are only a few rows of data in the example application, but these are expensive operations and a real application can grind to a halt as the content is repeatedly destroyed and re-created. The worst part of this problem is that all the changes except one were unnecessary because the content in the table didn’t need to be updated until the new Product object was added to the data model. For all the other changes, the directive destroyed its content and created an identical replacement.

Fortunately, Angular provides some tools for managing updates more efficiently and updating content only when it is required. The ngDoCheck method will still be called for all changes in the application, but the directive can inspect its data to see whether any changes that require new content have occurred, as shown in the following example.

Minimizing Content Changes in the iterator.directive.ts File in the src/app Folder

import { Directive, ViewContainerRef, TemplateRef,
 Input, SimpleChange, IterableDiffer, IterableDiffers,
 ChangeDetectorRef, CollectionChangeRecord, DefaultIterableDiffer
} from "@angular/core";
@Directive({
 selector: "[paForOf]"
})
export class PaIteratorDirective {
 private differ: DefaultIterableDiffer<any>;
 constructor(private container: ViewContainerRef,
 private template: TemplateRef<Object>,
 private differs: IterableDiffers,
 private changeDetector: ChangeDetectorRef) {
 }
 @Input("paForOf")
 dataSource: any;
 ngOnInit() {
 this.differ =
 <DefaultIterableDiffer<any>> this.differs.find(this.dataSource).create();
 }
 ngDoCheck() {
 let changes = this.differ.diff(this.dataSource);
 if (changes != null) {
 console.log("ngDoCheck called, changes detected");
 changes.forEachAddedItem(addition => {
 this.container.createEmbeddedView(this.template,
 new PaIteratorContext(addition.item,
 addition.currentIndex, changes.length));
 });
 }
 }
}
class PaIteratorContext {
 odd: boolean; even: boolean;
 first: boolean; last: boolean;
constructor(public $implicit: any,
 public index: number, total: number ) {
 this.odd = index % 2 == 1;
 this.even = !this.odd;
 this.first = index == 0;
 this.last = index == total - 1;
 }
}

The idea is to work out whether there have been objects added, removed, or moved from the collection. This means the directive has to do some work every time the ngDoCheck method is called to avoid unnecessary and expensive DOM operations when there are no collection changes to the process.

The process starts in the constructor, which receives two new arguments whose values will be provided by Angular when a new instance of the directive class is created. The IterableDiffers and ChangeDetectorRef objects are used to set up change detection on the data source collection in the ngOnInit method, like this:

...
ngOnInit() {
 this.differ =
 <DefaultIterableDiffer<any>> this.differs.find(this.dataSource).create();
}
...

Angular includes built-in classes, known as differs, that can detect changes in different types of objects. The IterableDiffers.find method accepts an object and returns an IterableDifferFactory object that is capable of creating a differ class for that object. The IterableDifferFactory class defines a create method that returns a DefaultIterableDiffer object that will perform the actual change detection, using the ChangeDetectorRef object that was received in the constructor.

The important part of this incantation is the DefaultIterableDiffer object, which was assigned to a property called differ so that it can be used when the ngDoCheck method is called.

...
ngDoCheck() {
 let changes = this.differ.diff(this.dataSource);
 if (changes != null) {
 console.log("ngDoCheck called, changes detected");
 changes.forEachAddedItem(addition => {
 this.container.createEmbeddedView(this.template,
 new PaIteratorContext(addition.item,
 addition.currentIndex, changes.length));
 });
 }
}
...

The DefaultIterableDiffer.diff method accepts an object for comparison and returns a list of the changes or null if there have been no changes. Checking for the null result allows the directive to avoid unnecessary work when the ngDoCheck method is called for changes elsewhere in the application. The object returned by the diff method provides the properties and methods described in the following table for processing changes.

The DefaultIterableDiffer.Diff Results Methods and Properties

Name Description
collection This property returns the collection of objects that have been inspected for changes.
length This property returns the number of objects in the collection.
forEachItem(func) This method invokes the specified function for each object in the collection.
forEachPreviousItem(func) This method invokes the specified function for each object in the previous version of the collection.
forEachAddedItem(func) This method invokes the specified function for each new object in the collection.
forEachMovedItem(func) This method invokes the specified function for each object whose position has changed.
forEachRemovedItem(func) This method invokes the specified function for each object that was removed from the collection.
forEachIdentityChange(func) This method invokes the specified function for each object whose identity has changed.

The functions that are passed to the methods described in the above table will receive a CollectionChangeRecord object that describes an item and how it has changed, using the properties shown in the following table.

The CollectionChangeRecord Properties

Name Description
item This property returns the data item
trackById This property returns the identity value if a trackBy function is used.
currentIndex This property returns the current index of the item in the collection.
previousIndex This property returns the previous index of the item in the collection.

Reference

https://angular.io/api/core/DoCheck

Visit the angular tutorial list. And make strong your angular concept. click here. wuschools.com is always written about the Agular concept for the angular lover. Ang writes about how angular makes your life easy if you are a web site developer.

Leave a Reply

Close Menu