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
7410941a7c
commit
81d64d6bec
@ -98,7 +98,7 @@ export function checkBindingNoChanges(
|
|||||||
view: ViewData, def: NodeDef, bindingIdx: number, value: any) {
|
view: ViewData, def: NodeDef, bindingIdx: number, value: any) {
|
||||||
const oldValue = view.oldValues[def.bindingIndex + bindingIdx];
|
const oldValue = view.oldValues[def.bindingIndex + bindingIdx];
|
||||||
if ((view.state & ViewState.BeforeFirstCheck) || !devModeEqual(oldValue, value)) {
|
if ((view.state & ViewState.BeforeFirstCheck) || !devModeEqual(oldValue, value)) {
|
||||||
const bindingName = def.bindings[def.bindingIndex].name;
|
const bindingName = def.bindings[bindingIdx].name;
|
||||||
throw expressionChangedAfterItHasBeenCheckedError(
|
throw expressionChangedAfterItHasBeenCheckedError(
|
||||||
Services.createDebugContext(view, def.nodeIndex), `${bindingName}: ${oldValue}`,
|
Services.createDebugContext(view, def.nodeIndex), `${bindingName}: ${oldValue}`,
|
||||||
`${bindingName}: ${value}`, (view.state & ViewState.BeforeFirstCheck) !== 0);
|
`${bindingName}: ${value}`, (view.state & ViewState.BeforeFirstCheck) !== 0);
|
||||||
|
@ -1156,11 +1156,19 @@ const TEST_COMPILER_PROVIDERS: Provider[] = [
|
|||||||
|
|
||||||
describe('enforce no new changes', () => {
|
describe('enforce no new changes', () => {
|
||||||
it('should throw when a record gets changed after it has been checked', fakeAsync(() => {
|
it('should throw when a record gets changed after it has been checked', fakeAsync(() => {
|
||||||
const ctx = createCompFixture('<div [someProp]="a"></div>', TestData);
|
@Directive({selector: '[changed]'})
|
||||||
ctx.componentInstance.a = 1;
|
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())
|
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(() => {
|
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'})
|
@Component({selector: 'root', template: 'empty'})
|
||||||
class TestData {
|
class TestData {
|
||||||
public a: any;
|
a: any;
|
||||||
|
b: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({selector: 'root', template: 'empty'})
|
@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'.`);
|
`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', () => {
|
it('should support detaching and attaching component views for dirty checking', () => {
|
||||||
class AComp {
|
class AComp {
|
||||||
a: any;
|
a: any;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user