Skip to content

Commit 81d64d6

Browse files
vicbjasonaden
authored andcommitted
fix(core): fix retrieving the binding name when an expression changes (#21814)
fixes #21735 fixes #21788 PR Close #21814
1 parent 7410941 commit 81d64d6

File tree

3 files changed

+45
-5
lines changed

3 files changed

+45
-5
lines changed

packages/core/src/view/util.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export function checkBindingNoChanges(
9898
view: ViewData, def: NodeDef, bindingIdx: number, value: any) {
9999
const oldValue = view.oldValues[def.bindingIndex + bindingIdx];
100100
if ((view.state & ViewState.BeforeFirstCheck) || !devModeEqual(oldValue, value)) {
101-
const bindingName = def.bindings[def.bindingIndex].name;
101+
const bindingName = def.bindings[bindingIdx].name;
102102
throw expressionChangedAfterItHasBeenCheckedError(
103103
Services.createDebugContext(view, def.nodeIndex), `${bindingName}: ${oldValue}`,
104104
`${bindingName}: ${value}`, (view.state & ViewState.BeforeFirstCheck) !== 0);

packages/core/test/linker/change_detection_integration_spec.ts

+13-4
Original file line numberDiff line numberDiff line change
@@ -1156,11 +1156,19 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
11561156

11571157
describe('enforce no new changes', () => {
11581158
it('should throw when a record gets changed after it has been checked', fakeAsync(() => {
1159-
const ctx = createCompFixture('<div [someProp]="a"></div>', TestData);
1160-
ctx.componentInstance.a = 1;
1159+
@Directive({selector: '[changed]'})
1160+
class ChangingDirective {
1161+
@Input() changed: any;
1162+
}
1163+
1164+
TestBed.configureTestingModule({declarations: [ChangingDirective]});
1165+
1166+
const ctx = createCompFixture('<div [someProp]="a" [changed]="b"></div>', TestData);
1167+
1168+
ctx.componentInstance.b = 1;
11611169

11621170
expect(() => ctx.checkNoChanges())
1163-
.toThrowError(/Expression has changed after it was checked./g);
1171+
.toThrowError(/Previous value: 'changed: undefined'\. Current value: 'changed: 1'/g);
11641172
}));
11651173

11661174
it('should warn when the view has been created in a cd hook', fakeAsync(() => {
@@ -1968,7 +1976,8 @@ class Uninitialized {
19681976

19691977
@Component({selector: 'root', template: 'empty'})
19701978
class TestData {
1971-
public a: any;
1979+
a: any;
1980+
b: any;
19721981
}
19731982

19741983
@Component({selector: 'root', template: 'empty'})

packages/core/test/view/component_view_spec.ts

+31
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,37 @@ const addEventListener = '__zone_symbol__addEventListener';
135135
`ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'a: v1'. Current value: 'a: v2'.`);
136136
});
137137

138+
// fixes https://github.com/angular/angular/issues/21788
139+
it('report the binding name when an expression changes after it has been checked', () => {
140+
let value: any;
141+
class AComp {}
142+
143+
const update =
144+
jasmine.createSpy('updater').and.callFake((check: NodeCheckFn, view: ViewData) => {
145+
check(view, 0, ArgumentType.Inline, 'const', 'const', value);
146+
});
147+
148+
const {view, rootNodes} = createAndGetRootNodes(
149+
compViewDef([
150+
elementDef(0, NodeFlags.None, null, null, 1, 'div', null, null, null, null, () => compViewDef([
151+
elementDef(0, NodeFlags.None, null, null, 0, 'span', null, [
152+
[BindingFlags.TypeElementAttribute, 'p1', SecurityContext.NONE],
153+
[BindingFlags.TypeElementAttribute, 'p2', SecurityContext.NONE],
154+
[BindingFlags.TypeElementAttribute, 'p3', SecurityContext.NONE],
155+
]),
156+
], null, update)
157+
),
158+
directiveDef(1, NodeFlags.Component, null, 0, AComp, []),
159+
]));
160+
161+
value = 'v1';
162+
Services.checkAndUpdateView(view);
163+
value = 'v2';
164+
expect(() => Services.checkNoChangesView(view))
165+
.toThrowError(
166+
`ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'p3: v1'. Current value: 'p3: v2'.`);
167+
});
168+
138169
it('should support detaching and attaching component views for dirty checking', () => {
139170
class AComp {
140171
a: any;

0 commit comments

Comments
 (0)