fix(core): fix retrieving the binding name when an expression changes (#21814)
fixes #21735 fixes #21788 PR Close #21814
This commit is contained in:
parent
dcca799dbb
commit
2af19c96f2
|
@ -98,7 +98,7 @@ export function checkBindingNoChanges(
|
|||
view: ViewData, def: NodeDef, bindingIdx: number, value: any) {
|
||||
const oldValue = view.oldValues[def.bindingIndex + bindingIdx];
|
||||
if ((view.state & ViewState.BeforeFirstCheck) || !devModeEqual(oldValue, value)) {
|
||||
const bindingName = def.bindings[def.bindingIndex].name;
|
||||
const bindingName = def.bindings[bindingIdx].name;
|
||||
throw expressionChangedAfterItHasBeenCheckedError(
|
||||
Services.createDebugContext(view, def.nodeIndex), `${bindingName}: ${oldValue}`,
|
||||
`${bindingName}: ${value}`, (view.state & ViewState.BeforeFirstCheck) !== 0);
|
||||
|
|
|
@ -1156,11 +1156,19 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
|
|||
|
||||
describe('enforce no new changes', () => {
|
||||
it('should throw when a record gets changed after it has been checked', fakeAsync(() => {
|
||||
const ctx = createCompFixture('<div [someProp]="a"></div>', TestData);
|
||||
ctx.componentInstance.a = 1;
|
||||
@Directive({selector: '[changed]'})
|
||||
class ChangingDirective {
|
||||
@Input() changed: any;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [ChangingDirective]});
|
||||
|
||||
const ctx = createCompFixture('<div [someProp]="a" [changed]="b"></div>', TestData);
|
||||
|
||||
ctx.componentInstance.b = 1;
|
||||
|
||||
expect(() => ctx.checkNoChanges())
|
||||
.toThrowError(/Expression has changed after it was checked./g);
|
||||
.toThrowError(/Previous value: 'changed: undefined'\. Current value: 'changed: 1'/g);
|
||||
}));
|
||||
|
||||
it('should warn when the view has been created in a cd hook', fakeAsync(() => {
|
||||
|
@ -1968,7 +1976,8 @@ class Uninitialized {
|
|||
|
||||
@Component({selector: 'root', template: 'empty'})
|
||||
class TestData {
|
||||
public a: any;
|
||||
a: any;
|
||||
b: any;
|
||||
}
|
||||
|
||||
@Component({selector: 'root', template: 'empty'})
|
||||
|
|
|
@ -135,6 +135,37 @@ const addEventListener = '__zone_symbol__addEventListener';
|
|||
`ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'a: v1'. Current value: 'a: v2'.`);
|
||||
});
|
||||
|
||||
// fixes https://github.com/angular/angular/issues/21788
|
||||
it('report the binding name when an expression changes after it has been checked', () => {
|
||||
let value: any;
|
||||
class AComp {}
|
||||
|
||||
const update =
|
||||
jasmine.createSpy('updater').and.callFake((check: NodeCheckFn, view: ViewData) => {
|
||||
check(view, 0, ArgumentType.Inline, 'const', 'const', value);
|
||||
});
|
||||
|
||||
const {view, rootNodes} = createAndGetRootNodes(
|
||||
compViewDef([
|
||||
elementDef(0, NodeFlags.None, null, null, 1, 'div', null, null, null, null, () => compViewDef([
|
||||
elementDef(0, NodeFlags.None, null, null, 0, 'span', null, [
|
||||
[BindingFlags.TypeElementAttribute, 'p1', SecurityContext.NONE],
|
||||
[BindingFlags.TypeElementAttribute, 'p2', SecurityContext.NONE],
|
||||
[BindingFlags.TypeElementAttribute, 'p3', SecurityContext.NONE],
|
||||
]),
|
||||
], null, update)
|
||||
),
|
||||
directiveDef(1, NodeFlags.Component, null, 0, AComp, []),
|
||||
]));
|
||||
|
||||
value = 'v1';
|
||||
Services.checkAndUpdateView(view);
|
||||
value = 'v2';
|
||||
expect(() => Services.checkNoChangesView(view))
|
||||
.toThrowError(
|
||||
`ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'p3: v1'. Current value: 'p3: v2'.`);
|
||||
});
|
||||
|
||||
it('should support detaching and attaching component views for dirty checking', () => {
|
||||
class AComp {
|
||||
a: any;
|
||||
|
|
Loading…
Reference in New Issue