Detect changes in objects inside array in Angular2

Using Angular 2 and typescript. I have an array that I use DoCheck and IterableDiffer to listen to changes in my code. When the array is changed I get notifications, but when a property inside one of the objects in the array changes I don't get notified. I tried using KeyValueDiffer but it doesn't work. I think maybe I need to use the "_differs.find" differently. Any ideas?

@Component({
    selector: 'test'
})
@View({
    template: `
    <div>hello!</div>`
})
export class MyComponent implements DoCheck {

private _differ: IterableDiffer;
private _differ2: KeyValueDiffer;
_myItems: MyItem[];
@Input()
set myItems(value: MyItem[]) {
    this._myItems = value;

    if (!this._differ && value) {
        this._differ = this._iterableDiffers.find([]).create(null);
    }
    if (!this._differ2 && value) {
        this._differ2 = this._differs.find(this._myItems).create(null);
    }
}
get myItems() {
    return this._myItems;
}

constructor(private _iterableDiffers: IterableDiffers, private _differs: KeyValueDiffers, private _myService: MyService) {
    this.myItems = _myService.getItems();
}

ngDoCheck() {

    var changes = this._differ.diff(this._myItems);

    if (changes) {
        changes.forEachAddedItem((record) => {
            console.log('added ' + record.item);
        });
        changes.forEachRemovedItem((record) => {
            console.log('removed ' + record.item);
        });
    }

    var changes2 = this._differ2.diff(this._myItems);
    if (changes2) {
        changes2.forEachChangedItem(
            (record) => {
                    console.log(record.key + "," + record.currentValue);
                }
            });
    }
  }
}

There is no way to get notified on changes in one of the properties of MyItem


Solution 1:

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.

Solution 2:

I found inspiration in Thierry Templiers answer but it did not work for me- I modified it and ended up with this solution:

You have this private variable:

 private objDiffers: Array<KeyValueDiffer<string, any>>;

You want to watch each element in an Array. In this case the array type is Array. The constructor should look like this:

  constructor(
    private differs: KeyValueDiffers) {
    this.itemGroups = new Array<ItemGroup>();
    this.differ = this.differs.find(this.itemGroups).create();
  }

ngOninit like this:

  public ngOnInit(): void {
    this.objDiffers = new Array<KeyValueDiffer<string, any>>();
    this.itemGroups.forEach((itemGroup, index) => {
      this.objDiffers[index] = this.differs.find(itemGroup).create();
    });
  }

and ngDoCheck like this:

ngDoCheck(): void {
    this.itemGroups.forEach((itemGroup, index) => {
      const objDiffer = this.objDiffers[index];
      const objChanges = objDiffer.diff(itemGroup);
      if (objChanges) {
        objChanges.forEachChangedItem((changedItem) => {
          console.log(changedItem.key);
        });
      }
    });
  }

Now you will notice that each time an item within the array changes you will get a console log with that item and with the property that has changed.