Angular 2: How to Detect Changes in an Array? (@Input Property)

Angular 2: How to detect changes in an array? (@input property)

OnChanges Lifecycle Hook will trigger only when input property's instance changes.

If you want to check whether an element inside the input array has been added, moved or removed, you can use IterableDiffers inside the DoCheck Lifecycle Hook as follows:

constructor(private iterableDiffers: IterableDiffers) {
this.iterableDiffer = iterableDiffers.find([]).create(null);
}

ngDoCheck() {
let changes = this.iterableDiffer.diff(this.inputArray);
if (changes) {
console.log('Changes detected!');
}
}

If you need to detect changes in objects inside an array, you will need to iterate through all elements, and apply KeyValueDiffers for each element. (You can do this in parallel with previous check).

Visit this post for more information: Detect changes in objects inside array in Angular2

How to detect when an @Input() value changes in Angular?

Actually, there are two ways of detecting and acting upon when an input changes in the child component in angular2+ :

  1. You can use the ngOnChanges() lifecycle method as also mentioned in older answers:
    @Input() categoryId: string;

ngOnChanges(changes: SimpleChanges) {

this.doSomething(changes.categoryId.currentValue);
// You can also use categoryId.previousValue and
// categoryId.firstChange for comparing old and new values

}

Documentation Links: ngOnChanges, SimpleChanges, SimpleChange

Demo Example: Look at this plunker


  1. Alternately, you can also use an input property setter as follows:
    private _categoryId: string;

@Input() set categoryId(value: string) {

this._categoryId = value;
this.doSomething(this._categoryId);

}

get categoryId(): string {

return this._categoryId;

}

Documentation Link: Look here.

Demo Example: Look at this plunker.

WHICH APPROACH SHOULD YOU USE?

If your component has several inputs, then, if you use ngOnChanges(), you will get all changes for all the inputs at once within ngOnChanges(). Using this approach, you can also compare current and previous values of the input that has changed and take actions accordingly.

However, if you want to do something when only a particular single input changes (and you don't care about the other inputs), then it might be simpler to use an input property setter. However, this approach does not provide a built in way to compare previous and current values of the changed input (which you can do easily with the ngOnChanges lifecycle method).

EDIT 2017-07-25: ANGULAR CHANGE DETECTION MAY STILL NOT FIRE UNDER SOME CIRCUMSTANCES

Normally, change detection for both setter and ngOnChanges will fire whenever the parent component changes the data it passes to the child, provided that the data is a JS primitive datatype(string, number, boolean). However, in the following scenarios, it will not fire and you have to take extra actions in order to make it work.

  1. If you are using a nested object or array (instead of a JS primitive data type) to pass data from Parent to Child, change detection (using either setter or ngchanges) might not fire, as also mentioned in the answer by user: muetzerich. For solutions look here.

  2. If you are mutating data outside of the angular context (i.e., externally), then angular will not know of the changes. You may have to use ChangeDetectorRef or NgZone in your component for making angular aware of external changes and thereby triggering change detection. Refer to this.

Detect changes in objects inside array in Angular2

In fact, you need to check differences for each object in the list not on the list itself. KeyValueDiffer must be applied on an object not on an array.

You could initialize an object containing all these KeyValueDiffer instances for the elements of your array:

constructor(private differs:  KeyValueDiffers) {
}

ngOnInit() {
this.objDiffer = {};
this.list.forEach((elt) => {
this.objDiffer[elt] = this.differs.find(elt).create(null);
});
}

Within the ngDoCheck, you need then to iterate over the differs to check there are changes (for each item of the array):

ngDoCheck() {
this.list.forEach(elt => {
var objDiffer = this.objDiffer[elt];
var objChanges = objDiffer.diff(elt);
if (objChanges) {
objChanges.forEachChangedItem((elt) => {
if (elt.key === 'prop1') {
this.doSomethingIfProp1Change();
}
});
}
});
}

See this plunkr: http://plnkr.co/edit/JV7xcMhAuupnSdwrd8XB?p=preview

Notice that I skipped the change detection for the array... But both can be done in parallel. Moreover when elements are added / removed the list of KeyValueDiffers should be updated accordingly.

Angular 2 change detection - push data into array

if you put ChangeDetectionStrategy.OnPush in children, you need use this.myArray = [...this.myArray, x]; -change the array- else you has no make any change.

the stackblitz

//hello, no change ChangeDetectionStrategy
@Component({
selector: 'hello',
template: `Hello <div *ngFor="let item of array">{{item}}</div>`,
styles: [`h1 { font-family: Lato; }`],
})
export class HelloComponent {
@Input() array: any[];
}

//hello2, ChangeDetectionStrategy
@Component({
selector: 'hello2',
template: `Hello2 <div *ngFor="let item of array">{{item}}</div>`,
styles: [`h1 { font-family: Lato; }`],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class HelloComponent2 {
@Input() array: any[];
}

main.component:

<hello  [array]="array"></hello>
<hr/>
<hello2 [array]="array"></hello2>

<button (click)="click()">push</button>
<button (click)="click2()">CopyArray</button>

array=[1,2,3,4]
click2(){
this.array=[...this.array,1]
}
click(){
this.array.push(1)
}

Update there another way

In main component:

  constructor(private cdr:ChangeDetectorRef){}
click3(){
this.array.push(1)
this.cdr.detectChanges()
}

Which is the best way to track changes in an array inside a component?

I would do it using ngOnChanges() which is one of the Angular lifecycle hook. Where you can find information like:

  1. If it is the first change.
  2. What's the current value.
  3. What's the previous value.

It is of type : SimpleChange

ngOnChanges(changes: SimpleChanges){
// Detect Changes
let myArrayChange = changes['myArray'];
let currentValue = myArrayChange.currentValue;
let previousValue = myArrayChange.previousValue;
}

Also, Angular change detection only checks object identity, not object content, so push, pop are not detected. So what you can do is in the Child Component where you are assigning this input, while pushing, push different object by using slice() method of Array

Child Component HTML:

<test myArray="arr"></test>

Child Component TS:

export class Child{

arr: any = [];

someMethod(){
this.arr.push('new element');
this.arr = this.arr.slice();
}
}


Related Topics



Leave a reply



Submit