问题
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