Angular2 ngFor OnPush Change Detection with Array Mutations

前端 未结 4 1895
深忆病人
深忆病人 2020-12-03 04:00

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

相关标签:
4条回答
  • 2020-12-03 04:03

    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

    0 讨论(0)
  • 2020-12-03 04:23

    Late Answer but another workaround is by using the spread operator after mutation.. myArr = [...myArr] or myObj = {...myObj}

    This can even be done while mutating: myArr = myMutatingArr([...myArr]) since the parameter is taken as a new reference of an Array, making the variable take a new reference, and therefore the Angular check is called.

    As mentioned, if you change the reference, the check will be made, the spread operator can be used in any case to do exactly that.

    Be wary though that nested structures of data inside structures of data require the reference change up to the nested level. You would have to do an iteration that returns a spread inside spread, as such:

    myObj = {...myObj, propToChange: { ...myObj.propToChange,
            nestedPropArr: [ ...myObj.propToChange.nestedPropArr ]
        }
    }
    

    which might become complicated if you need iteration over objects and such. Hope this helps someone!

    0 讨论(0)
  • 2020-12-03 04:24

    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

    0 讨论(0)
  • 2020-12-03 04:28

    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();

    0 讨论(0)
提交回复
热议问题