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.