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+ :
- 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
- 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.
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.
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 differ
s 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:
- If it is the first change.
- What's the current value.
- 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
How to Test If a Variable Does Not Equal Either of Two Values
Chrome Extension - How to Get Http Response Body
What Is "Context" in Jquery Selector
Which Characters Are Valid/Invalid in a JSON Key Name
Replace a Regex Capture Group with Uppercase in JavaScript
How to Delete Document from Firestore Using Where Clause
How to Scrape Pages with Dynamic Content Using Node.Js
JavaScript Syntax (0, Fn)(Args)
Angular2:Render a Component Without Its Wrapping Tag
Using HTML Comment Tag <!-- --> Still Relevant Around JavaScript Code
Javascript: How to Validate Dates in Format Mm-Dd-Yyyy
Save File JavaScript with File Name
JavaScript Getcookie Functions
Keyword 'Const' Does Not Make the Value Immutable. What Does It Mean
Keys in JavaScript Objects Can Only Be Strings
How to Find JavaScript Variable by Its Name
How to Create Every Combination Possible for the Contents of Two Arrays