Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular2 ngFor OnPush Change Detection with Array Mutations

I have a data table component ( angular2-data-table ) project where we changed the project from Angular's traditional change detection to OnPush for optimized rendering speeds.

Once the new change detection strategy was implemented, a bug was filed referencing the table is not updating when the data object is mutated such as object's property updates Reference: https://github.com/swimlane/angular2-data-table/issues/255. A strong use case can be made for this type of need for things such as inline editing or external data changes to a single property in a large data collection like a stock ticker.

In an effort to resolve the issue, we added a custom trackBy property checker called trackByProp. Reference: commit. Unfortunately, this solution did not resolve the matter.

On the demo page under live reloading you can see the demo referenced in the above commit running but not updating the table until you click thus triggering change detection.

The structure of the component is something like:

Table > Body > Row Group > Row > Cell

all of these components implementOnPush. I'm using getters/setters in the row setter to trigger page recalculations like shown here.

We'd like to stay with the OnPushchange detection for those implementing this pattern, however, as a open-source project with multiple consumers one could argue some sort of custom checking function for the visible row values on the screen.

All that said, trackBy is not triggering change detection in row cell values, what is the best way to accomplish this?

like image 491
amcdnl Avatar asked Nov 27 '16 14:11

amcdnl


People also ask

What triggers OnPush change detection?

Input Reference But with OnPush strategy, the change detector is only triggered if the data passed on @Input() has a new reference. This is why using immutable objects is preferred, because immutable objects can be modified only by creating a new object reference.

In which situations will angular check for changes in a component using the OnPush change detection strategy?

Angular must be notified about changes when the OnPush strategy is used. Some notifications are built into the system – that is one reason for the immutable objects. By default, the @Input() decorator and the async pipe mark the component to be checked if the object changes.

What is the difference between OnPush and default change detection?

OnPush means that the change detector's mode will be set to CheckOnce during hydration. Default means that the change detector's mode will be set to CheckAlways during hydration.

What is the default change detection strategy for a component?

Default change detection strategy is applied to the component while it is created. If a component strategy is not configured, it is marked as default. In this strategy, the change detection cycle runs on each and every event that occur inside the Component.


2 Answers

Angular2 change detection doesn't check the contents of arrays or object.

A hacky workaround is to just create a copy of the array after mutation

this.myArray.push(newItem);
this.myArray = this.myArray.slice();

This way this.myArray refers a different array instance and Angular will recognize the change.

Another approach is to use an IterableDiffer (for arrays) or KeyValueDiffer (for objects)

// inject a differ implementation 
constructor(differs: KeyValueDiffers) {
  // store the initial value to compare with
  this.differ = differs.find({}).create(null);
}

@Input() data: any;

ngDoCheck() {
  var changes = this.differ.diff(this.data); // check for changes
  if (changes && this.initialized) {
    // do something if changes were found
  }
}

See also https://github.com/angular/angular/blob/14ee75924b6ae770115f7f260d720efa8bfb576a/modules/%40angular/common/src/directives/ng_class.ts#L122

like image 56
Günter Zöchbauer Avatar answered Sep 27 '22 18:09

Günter Zöchbauer


You might want to use markForCheck method from ChangeDetectorRef.


I do have a similar issue, where I do have a component that contains a lot of data and re-check them all on every change detection cycle is not an option. But as we watch some properties from URL and we change things in the view accordingly, with onPush our view is not refreshed (automatically).

So in your constructor, use DI to get an instance of changeDetectorRef : constructor(private changeDetectorRef: ChangeDetectorRef)

And wherever you need to trigger a changeDetection : this.changeDetectorRef.markForCheck();

like image 20
maxime1992 Avatar answered Sep 27 '22 17:09

maxime1992