Angular2 ngFor OnPush Change Detection with Array Mutations
Solution 1
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
}
}
Solution 2
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();
Solution 3
I too faced the similar issue where to optimize my app performance, I had to use the changeDetection.OnPush strategy. So I injected it in both my parent component as well as my child component's constructor , the instance of changeDetectorRef
export class Parentcomponent{
prop1;
constructor(private _cd : ChangeDetectorRef){
}
makeXHRCall(){
prop1 = ....something new value with new reference;
this._cd.markForCheck(); // To force angular to trigger its change detection now
}
}
Similarly in child component, injected the instance of changeDetectorRef
export class ChildComponent{
@Input myData: myData[];
constructor(private _cd : ChangeDetectorRef){
}
changeInputVal(){
this.myData = ....something new value with new reference;
this._cd.markForCheck(); // To force angular to trigger its change detection now
}
}
Angular change Detection is triggered on every asynchronous function :-
- any DOM event like click,submit, mouseover.
- any XHR call
- any Timers like setTimeout(), etc.
So, this kind of slows down the app because even when we are dragging the mouse, angular was triggering changeDetection. For a complex app spanning over multiple components, this could be a major performance bottleneck since angular has this tree kind of parent to child change detection strategy. To avoid, this it is better we use the OnPush strategy and forcefully trigger angular's change detection where we see there is a reference change.
Secondly, in OnPush strategy, one should be very careful that it will only trigger change when there is a change in object reference and not just the object property value i.e in Angular change” means “new reference”.
For eg:-
obj = { a:'value1', b:'value2'}'
obj.a = value3;
The property value of 'a' in 'obj' might have change but obj still points to the same reference, so Angular change detection will not trigger here (unless you force it to); To create a new reference , need to clone the object into another object and assign its properties accordingly.
for further understanding, read about Immmutable Data structures, change Detection here
amcdnl
obsessed with the web / security / tech follow me on github or twitter: @amcdnl
Updated on June 08, 2020Comments
-
amcdnl about 4 years
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 implement
OnPush
. I'm using getters/setters in the row setter to trigger page recalculations like shown here.We'd like to stay with the
OnPush
change 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? -
amcdnl over 7 years
ngDoCheck
isn't running until I invoke an event like click. Also, I noticed that*ngFor
has custom tracking already in it, why wouldn't this detect it? github.com/angular/angular/blob/master/modules/%40angular/… -
amcdnl over 7 yearsI was also investigating using Rx for this like: stackoverflow.com/questions/32683488/…
-
Günter Zöchbauer over 7 yearsAs mentioned above, the question should provide enough information to be able to reproduce. My answer was just a shoot from the hip. A Plunker to reproduce would be helpful. Perhaps it's a zone issue.
-
amcdnl over 7 yearsYa, I'll throw up a plunkr here in a few hrs :)
-
amcdnl over 7 yearsI tried this in the parent page and had no results. Perhaps it needs to be done at the component level thats rendering them but at that point I don't know anything changed :S
-
amcdnl over 7 yearsI suppose some sort of custom api to modify the property could do this? But thats not very nice from API perspective.
-
Marco Castano almost 7 yearsthe hacky workaround saved me ! thx you! EDIT : it works also this.myArray = this.myArray
-
Günter Zöchbauer about 5 years@AshishSharma why do you think so? A different copy is only assigned if it actually changed. You can also make your app smarter about changes to not have to compare every time which can become slow with big data structures.