Angular KeyValueDiffers + Detect Changes in Objects
August 15, 2019
This page will walk through Angular KeyValueDiffers
to detect changes in objects. KeyValueDiffers
is the repository of different map diffing strategies. Angular uses KeyValueDiffers
internally for directives NgClass
, NgStyle
to detect any change. We can also use KeyValueDiffers
to perform custom change-detection in our application. Angular provides ngDoCheck()
lifecycle hook to detect custom changes. For any type of change, ngDoCheck()
will be called and to identify the type of change and the object, we can use Angular KeyValueDiffers
and IterableDiffers
. Angular uses IterableDiffers
in NgFor
, NgClass
directive internally to detect changes.
On this page we will create an example to detect changes in array of objects and changes in property value of objects inside array using
KeyValueDiffers
. Now find the complete example step-by-step.
Contents
Technologies Used
Find the technologies being used in our example.1. Angular 8.0.3
2. TypeScript 3.4.3
3. Node.js 12.5.0
4. Angular CLI 8.0.6
ngDoCheck()
ngDoCheck()
is a lifecycle hook that works as a custom change-detection function in addition to check performed by the default change-detector. ngDoCheck()
is called during every change-detection run immediately after ngOnChanges()
and ngOnInit()
lifecycle hook. To use ngDoCheck()
, our components need to implement DoCheck
.
@Component({ selector: 'app-emp', ------ }) export class EmployeeComponent implements DoCheck { ngDoCheck() { let empListChanges = this.listDiffer.diff(this.empArray); if (empListChanges) { console.log('... Array changes ...'); }); } }
KeyValueDiffers
KeyValueDiffers
is the repository of different map diffing strategies. Angular uses it internally for directives NgClass
, NgStyle
etc. Whenever there is change in binding values of these directives, the changes reflects. To handle and update the changes, Angular uses KeyValueDiffers
.
In the same way, Angular provides
IterableDiffers
that is the repository of different iterable diffing strategies used by directives NgFor
, NgClass
etc. Here on this page we will discuss using KeyValueDiffers
with examples. In our example, we will detect changes in our user objects and notify. Find the steps to detect changes using KeyValueDiffers
.
1.
KeyValueDiffers
can be injected into component using constructor.
constructor(private kvDiffers: KeyValueDiffers) {}
KeyValueDiffers
provides find
method.
find(kv: any): KeyValueDifferFactory
find
method returns KeyValueDifferFactory
.
3.
KeyValueDifferFactory
provides factory for KeyValueDiffer
. To get KeyValueDiffer
instance, KeyValueDifferFactory
provides create
method.
create<K, V>(): KeyValueDiffer<K, V>
KeyValueDiffer
.
this.arrayDiffer = this.kvDiffers.find(this.empArray).create();
arrayDiffer
is the instance of KeyValueDiffer
.
4.
KeyValueDiffer
is a differ that tracks changes made to an object over time. It has a diff
method to compute a difference between the previous state and the new object state.
diff(object: { [key: string]: V; }): KeyValueChanges<string, V> | null
KeyValueChanges
.
let empArrayChanges = this.arrayDiffer.diff(this.empArray);
empArrayChanges
is the instance of KeyValueChanges
.
5.
KeyValueChanges
keeps the changes in the map since the last time the method diff
was invoked. KeyValueChanges
has following methods.
forEachItem: Iterates over all changes.
forEachPreviousItem: Iterates over previous items that has changed.
forEachChangedItem: Iterates those items whose value is changed.
forEachAddedItem: Iterates over all added items.
forEachRemovedItem: Iterates over all removed items.
All the above methods of
KeyValueChanges
provides KeyValueChangeRecord
item in iteration.
6.
KeyValueChangeRecord
is the record representing the item change information. KeyValueChangeRecord
has properties such as currentValue
and previousValue
. To get current value, we call its currentValue
property and to get previous value, we call its previousValue
property. Find the code snippet.
if (empArrayChanges) { empArrayChanges.forEachAddedItem((record) => { let emp = record.currentValue; console.log('Added ' + emp.name); }); }
Detect Changes in Array
Suppose we have an array ofEmployee
class and we want to detect changes in the array whenever an item is added and removed. We can detect changes using KeyValueDiffers
as following.
@Input() empArray: Employee[]; constructor(private kvDiffers: KeyValueDiffers) {} ngOnInit() { this.arrayDiffer = this.kvDiffers.find(this.empArray).create(); } ngDoCheck() { let empArrayChanges = this.arrayDiffer.diff(this.empArray); if (empArrayChanges) { empArrayChanges.forEachAddedItem((record) => { let emp = record.currentValue; console.log('Added ' + emp.name); }); empArrayChanges.forEachRemovedItem((record) => { let emp = record.previousValue; console.log('Removed ' + emp.name); }); } }
Employee
object of the array changes value of its any property.
Detect Changes in Objects inside Array
Suppose we have an array ofEmployee
class and we want to detect changes when any Employee
object of the array changes value of its any property. We can detect changes using KeyValueDiffers
as following.
@Input() empArray: Employee[]; empDifferMap = new Map<number, any>(); empMap = new Map<number, Employee>(); arrayDiffer: any; constructor(private kvDiffers: KeyValueDiffers) {} ngOnInit() { this.empArray.forEach(emp => { this.empDifferMap[emp.id] = this.kvDiffers.find(emp).create(); this.empMap[emp.id] = emp; }) } ngDoCheck() { for (let [key, empDiffer] of this.empDifferMap) { let empChanges = empDiffer.diff(this.empMap.get(key)); if (empChanges) { empChanges.forEachChangedItem(record => { console.log('Previous value: ' + record.previousValue); console.log('Current value: ' + record.currentValue); }); } } }
Complete Example
employee.tsexport class Employee { constructor(public id: number, public name: string){} }
import { Component, DoCheck, KeyValueDiffers, OnInit, Input } from '@angular/core'; import { Employee } from './employee'; @Component({ selector: 'app-emp', template: ` <h3>Change Logs</h3> <div *ngFor="let log of changeLogs"> {{log}} </div> ` }) export class EmployeeComponent implements DoCheck, OnInit { @Input() empArray: Employee[]; empDifferMap = new Map<number, any>(); empMap = new Map<number, Employee>(); arrayDiffer: any; changeLogs: string[] = []; constructor(private kvDiffers: KeyValueDiffers) { } ngOnInit() { this.arrayDiffer = this.kvDiffers.find(this.empArray).create(); this.empArray.forEach(emp => { this.empDifferMap[emp.id] = this.kvDiffers.find(emp).create(); this.empMap[emp.id] = emp; }) } ngDoCheck() { //Detect changes in array when item added or removed let empArrayChanges = this.arrayDiffer.diff(this.empArray); if (empArrayChanges) { console.log('... Array changes ...'); this.changeLogs.push('... Array changes ...'); empArrayChanges.forEachAddedItem((record) => { let emp = record.currentValue; this.empDifferMap.set(emp.id, this.kvDiffers.find(emp).create()); this.empMap.set(emp.id, emp); console.log('Added ' + emp.name); this.changeLogs.push('Added ' + emp.name); }); empArrayChanges.forEachRemovedItem((record) => { let emp = record.previousValue; this.empDifferMap.delete(emp.id); this.empMap.delete(emp.id); console.log('Removed ' + emp.name); this.changeLogs.push('Removed ' + emp.name); }); } //Detect changes in object inside array for (let [key, empDiffer] of this.empDifferMap) { let empChanges = empDiffer.diff(this.empMap.get(key)); if (empChanges) { empChanges.forEachChangedItem(record => { console.log('--- Employee with id ' + key + ' updated ---'); this.changeLogs.push('--- Employee with id ' + key + ' updated ---'); console.log('Previous value: ' + record.previousValue); this.changeLogs.push('Previous value: ' + record.previousValue); console.log('Current value: ' + record.currentValue); this.changeLogs.push('Current value: ' + record.currentValue); }); } } } }
import { Component, OnInit } from '@angular/core'; import { Employee } from './employee'; @Component({ selector: 'app-root', template: ` <button (click)="add()">Add</button> <br/> <button (click)="delete()">Delete</button> <br/> <button (click)="update()">Update</button> <app-emp [empArray]="empArray"></app-emp> ` }) export class AppComponent implements OnInit { empArray = []; index = 102; ngOnInit() { this.empArray.push(new Employee(100, "Mahesh")); this.empArray.push(new Employee(101, "Krishna")); } add() { this.empArray.push(new Employee(this.index++, "ABCD")); console.log('Employee added: ' + JSON.stringify(this.empArray)); } delete() { if (this.empArray && this.empArray.length > 2) { this.empArray.pop(); console.log('Employee deleted: ' + JSON.stringify(this.empArray)); } else { console.log('No further delete.'); } } nameCount = 1; update() { this.empArray[this.empArray.length - 1].name = "Shiva" + this.nameCount++; } }
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { AppComponent } from './app.component'; import { EmployeeComponent } from './employee.component'; @NgModule({ imports: [ BrowserModule, FormsModule ], declarations: [ AppComponent, EmployeeComponent ], providers: [ ], bootstrap: [ AppComponent ] }) export class AppModule { }
Run Application
To run the application, find the steps.1. Download source code using download link given below on this page.
2. Use downloaded src in your Angular CLI application. To install Angular CLI, find the link.
3. Run ng serve using command prompt.
4. Access the URL http://localhost:4200
Click on the add, delete and update button and we can see the logs of change-detection.

References
Angular doc: KeyValueDiffersAngular Lifecycle Hooks