Skip to content

Commit

Permalink
refactor(core): Improve ExpressionChangedAfterItHasBeenCheckedError
Browse files Browse the repository at this point in the history
Related to angular#50272 and angular#18970, this improves the error message of NG100 by including the class name of the component where the error was triggered.
  • Loading branch information
JeanMeche committed May 15, 2023
1 parent fab7f39 commit 8ae3fed
Show file tree
Hide file tree
Showing 3 changed files with 10 additions and 5 deletions.
2 changes: 1 addition & 1 deletion packages/core/src/render3/bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export function bindingUpdated(lView: LView, bindingIndex: number, value: any):
const details =
getExpressionChangedErrorDetails(lView, bindingIndex, oldValueToCompare, value);
throwErrorIfNoChangesMode(
oldValue === NO_CHANGE, details.oldValue, details.newValue, details.propName);
oldValue === NO_CHANGE, details.oldValue, details.newValue, details.propName, lView);
}
// There was a change, but the `devModeEqual` decided that the change is exempt from an error.
// For this reason we exit as if no change. The early exit is needed to prevent the changed
Expand Down
9 changes: 7 additions & 2 deletions packages/core/src/render3/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {RuntimeError, RuntimeErrorCode} from '../errors';
import {Type} from '../interface/type';

import {getComponentDef} from './definition';
import {getDeclarationComponentDef} from './instructions/element_validation';
import {TNode} from './interfaces/node';
import {LView, TVIEW} from './interfaces/view';
import {INTERPOLATION_DELIMITER} from './util/misc_utils';
Expand Down Expand Up @@ -52,11 +53,15 @@ export function throwMultipleComponentError(

/** Throws an ExpressionChangedAfterChecked error if checkNoChanges mode is on. */
export function throwErrorIfNoChangesMode(
creationMode: boolean, oldValue: any, currValue: any, propName?: string): never {
creationMode: boolean, oldValue: any, currValue: any, propName: string|undefined,
lView: LView): never {
const hostComponentDef = getDeclarationComponentDef(lView);
const componentClassName = hostComponentDef?.type?.name;
const field = propName ? ` for '${propName}'` : '';
let msg =
`ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value${
field}: '${oldValue}'. Current value: '${currValue}'.`;
field}: '${oldValue}'. Current value: '${currValue}'.${
componentClassName ? ` Expression location: ${componentClassName} component` : ''}`;
if (creationMode) {
msg +=
` It seems like the view has been created after its parent and its children have been dirty checked.` +
Expand Down
4 changes: 2 additions & 2 deletions packages/core/test/acceptance/exports_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ describe('exports', () => {
fixture.detectChanges();
})
.toThrowError(
/ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked/);
/ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.*AppComp/);
});

it('should not support reference on the same node', () => {
Expand All @@ -66,7 +66,7 @@ describe('exports', () => {
fixture.detectChanges();
})
.toThrowError(
/ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked/);
/ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.*AppComp/);
});

it('should support input referenced by host binding on that directive', () => {
Expand Down

0 comments on commit 8ae3fed

Please sign in to comment.