fix(core): fix retrieving the binding name when an expression changes (#21814)

fixes #21735
fixes #21788

PR Close #21814
This commit is contained in:
Victor Berchet 2018-01-26 14:36:11 -08:00 committed by Jason Aden
parent dcca799dbb
commit 2af19c96f2
3 changed files with 45 additions and 5 deletions

View File

@ -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);

View File

@ -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'})

View File

@ -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;