43

My angular application stuck with a issue, i used input as array and pushed a value to the array when the click event arise. But the ngOnChanges not firing when the array push is done. Is there a way to fire ngOnChange

My Code is ts file is

@Component({
  selector: 'adv-search',
  templateUrl: './app/modules/result.html'
})

export class InputComponent {
  @Input() metas:any=[];

  ngOnChanges() {
    console.log(this.metas);
  }
}

My Selector Tag

<adv-search [metas] = "metaIds"></adv-search>

Click Event Code

insertIds(id:any) {
   metaIds.push(id);
}
Claies
  • 22,124
  • 4
  • 53
  • 77
Manush
  • 1,852
  • 7
  • 26
  • 39

3 Answers3

86

Angular change detection only checks object identity, not object content. Inserts or removals are therefore not detected.

What you can do is to copy the array after each update

insertIds(id:any) {
  this.metaIds.push(id);
  this.metaIds = this.metaIds.slice();
}

or use IterableDiffer to check for changes inside InputComponent in ngDoCheck like NgFor does.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • whats the meaning of copy array here ? – Pardeep Jain Apr 05 '17 at 06:45
  • 1
    The array will be another one (another object instance) but with the original content. Angular will recognize this change. – Günter Zöchbauer Apr 05 '17 at 06:46
  • 1
    are you sure that by doing so angular will recognize the changes ? – Pardeep Jain Apr 05 '17 at 06:50
  • @PardeepJain thanks for the hint, didn't pay attention. Updated my answer. – Günter Zöchbauer Apr 05 '17 at 06:54
  • is this the same reason that angular won't rerender for a pure pipe? – Pengyy Apr 05 '17 at 06:55
  • @Pengyy yes, the same reason. Angular calls a pipe when the value or parameters passed to the pipe change. If it doesn't recognize a change, it won't call the pipe. An impure pipe is called every time change detection runs, no matter if there was a change. – Günter Zöchbauer Apr 05 '17 at 06:55
  • now it will work @GünterZöchbauer , well explained answer :) – Pardeep Jain Apr 05 '17 at 06:58
  • @GünterZöchbauer : I tried this, sure now it started reflecting the change everywhere, but it doesn't trigger the `ngOnChanges` function. This got triggered only when the value was first initialized. Why is this so? – Temp O'rary Oct 26 '17 at 12:20
  • @TempO'rary `ngOnChanges` is not supposed to be called when you modify an `@Input()`, `ngOnChanges()` only is called when Angular detects for a binding like `[metas]="xxx"` that `xxx` was modified and updates `@Input() metas;`. You can just make `metas` a getter/setter and put your code in the setter, then it is called every time the setter is called by your custom code or because Angular change detection noticed a change. – Günter Zöchbauer Oct 26 '17 at 13:15
  • @GünterZöchbauer I tried the getter/setter thing as per your suggestion. See it here (http://plnkr.co/edit/NSDC8Wi4sKcC5ub1PINB?p=preview). However, it still does not go to the `ngOnChanges`. I find that there is a difference how reference type and primitive types work. In my case its an object like in the example. Can you please guide how to get this example working? – Temp O'rary Oct 27 '17 at 06:45
  • What is the expected behavior? What doesn't work as you expect? Angular doesn't care about changes of properties of objects if the properties are not bound directly. – Günter Zöchbauer Oct 27 '17 at 06:47
  • Expected behaviour: I want to catch whenever value of the object changes somehow. I'm expecting it in the `ngOnChange` function. Similar to `$watchCollection` in AngularJS. However, it doesn't seem to happen that way. So, in the code that I've shared what needs to be changed? – Temp O'rary Oct 27 '17 at 06:58
  • 1
    There is nothing like that in Angular, mostly for performance reasons. The best way is usually to have an Observable in the object and subscribe to it where you want to get notified. For arrays, there is https://angular.io/api/core/IterableDiffers. You can check the source of `*ngFor` about how to use it. – Günter Zöchbauer Oct 27 '17 at 06:59
  • Out of context... Wasted hours on this issue... Thanks! – Carlo Luther Mar 05 '18 at 05:18
12

If you reassign your metaIds array, the ngOnChanges lifecycle event will be fired. You can deconstruct your array into a new array.

insertIds(id:any){
    this.metaIds = [...this.metaIds, id];
}
KyleGill
  • 171
  • 1
  • 3
11

@GünterZöchbauer 's answer is completely correct. However, I would like to add a alternative/supplement to copying the whole array. Using the same example from the question.

<adv-search [metas] = "metaIds"></adv-search>

altered to

<adv-search [metas] = "{trackBy: metaIds.length, metaIds: metaIds}"></adv-search>

Where length here is the equivalent of the *ngFor trackBy function for the component. With this the ngOnChanges occurs with content length changes.

If used in combination with the *ngFor trackBy method there should be some performance benefits as the whole array's bound templates are not recalculated (no screen/content flickering).

JerradPatch
  • 111
  • 4