Angular2 detect changes using value equivalence or reference equality?

坚强是说给别人听的谎言 提交于 2019-12-13 15:15:53

问题


I am using Angular2-RC.1 and I have seen a poor performance when I setup a component having large data. I have a tabular component (wrapping Handsontable) and I expose a bindable Input property called "data". This property is usually bound to a large array (around one hundred thousand rows).

When I set my large dataset the change detection is causing a test of value equivalence over the whole array in the host component (not the owner of the input property).

@Component({
    selector: "ha-spreadsheet",
    template: "<hot-table [data]="data"></hot-table>",
    directives: [ HotTable ],
    encapsulation: ViewEncapsulation.Emulated
})
export class Spreadsheet implements OnActivate {
    data: { rows: Array<Array<number>> };
    load(service) { this.data = service.getLargeDataSet(); }
}

Here I show a callstack showing that the change detection is launched over the whole data. (the bold method is the runtime auto-generated change detection function for my host component) instead to simply compare the references.

Is this the intented behavior?


回答1:


I have found the answer by myself. The standalone change detection process is comparing references (this is its behavior by design).

BUT when Production mode is NOT enabled then additional assertions perform equivalence testing over your component's data.




回答2:


Although @Jairo already answered the question, I want to document in more detail the code flow that he mentioned in a comment on his answer (so I don't have to dig through the source code again to find this):

During change detection, this code from view_utils.ts executes:

export function checkBinding(throwOnChange: boolean, oldValue: any, newValue: any): boolean {
  if (throwOnChange) {  // <<-------  this is set to true in devMode
    if (!devModeEqual(oldValue, newValue)) {
      throw new ExpressionChangedAfterItHasBeenCheckedException(oldValue, newValue, null);
    }
    return false;
  } else {
    return !looseIdentical(oldValue, newValue);  // <<--- so this runs in prodMode
  }
}

From change_detection_util.ts, here is the method that only runs in devMode:

export function devModeEqual(a: any, b: any): boolean {
  if (isListLikeIterable(a) && isListLikeIterable(b)) {
    return areIterablesEqual(a, b, devModeEqual);  // <<--- iterates over all items in a and b!
  } else if (!isListLikeIterable(a) && !isPrimitive(a) && !isListLikeIterable(b) &&
             !isPrimitive(b)) {
    return true;
  } else {
    return looseIdentical(a, b);
  }
}

So if a template binding contains something that is iterable – e.g., [arrayInputProperty]="parentArray"then in devMode change detection actually iterates through all of the (e.g. parentArray) items and compares them, even if there isn't an NgFor loop or something else that creates a template binding to each element. This very different from the looseIdentical() check that is performed in prodMode. For very large iterables, this could have a significant performance impact, as in the OP scenario.

areIterablesEqual() is in collection.ts and it simply iterates over the iterables and compares each item. (Since there is nothing interesting going on, I did not include the code here.)

From lang.ts (this is what I think most of us thought change detection always and only did -- in devMode or prodMode):

export function looseIdentical(a, b): boolean {
  return a === b || typeof a === "number" && typeof b === "number" && isNaN(a) && isNaN(b);
}

Thanks @Jairo for digging into to this.

Note to self: to easily find the auto-generated change detection object that Angular creates for a component, put {{aMethod()}} in the template and set a breakpoint inside the aMethod() method. When the breakpoint triggers, the View*.detectChangesInternal() methods should be on the call stack.



来源:https://stackoverflow.com/questions/37353790/angular2-detect-changes-using-value-equivalence-or-reference-equality

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!