feat(ExpressionChangedAfterItHasBeenCheckedException): more meaningful error message
fixes #9882
This commit is contained in:
parent
eacc9e6541
commit
2de8364de2
|
@ -11,7 +11,9 @@ import {isPrimitive, looseIdentical} from '../facade/lang';
|
||||||
|
|
||||||
export {looseIdentical} from '../facade/lang';
|
export {looseIdentical} from '../facade/lang';
|
||||||
|
|
||||||
export const UNINITIALIZED = new Object();
|
export const UNINITIALIZED = {
|
||||||
|
toString: () => 'CD_INIT_VALUE'
|
||||||
|
};
|
||||||
|
|
||||||
export function devModeEqual(a: any, b: any): boolean {
|
export function devModeEqual(a: any, b: any): boolean {
|
||||||
if (isListLikeIterable(a) && isListLikeIterable(b)) {
|
if (isListLikeIterable(a) && isListLikeIterable(b)) {
|
||||||
|
|
|
@ -6,8 +6,10 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {UNINITIALIZED} from '../change_detection/change_detection_util';
|
||||||
import {BaseException, WrappedException} from '../facade/exceptions';
|
import {BaseException, WrappedException} from '../facade/exceptions';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An error thrown if application changes model breaking the top-down data flow.
|
* An error thrown if application changes model breaking the top-down data flow.
|
||||||
*
|
*
|
||||||
|
@ -44,9 +46,14 @@ import {BaseException, WrappedException} from '../facade/exceptions';
|
||||||
*/
|
*/
|
||||||
export class ExpressionChangedAfterItHasBeenCheckedException extends BaseException {
|
export class ExpressionChangedAfterItHasBeenCheckedException extends BaseException {
|
||||||
constructor(oldValue: any, currValue: any, context: any) {
|
constructor(oldValue: any, currValue: any, context: any) {
|
||||||
super(
|
let msg =
|
||||||
`Expression has changed after it was checked. ` +
|
`Expression has changed after it was checked. Previous value: '${oldValue}'. Current value: '${currValue}'.`;
|
||||||
`Previous value: '${oldValue}'. Current value: '${currValue}'`);
|
if (oldValue === UNINITIALIZED) {
|
||||||
|
msg +=
|
||||||
|
` It seems like the view has been created after its parent and its children have been dirty checked.` +
|
||||||
|
` Has it been created in a change detection hook ?`;
|
||||||
|
}
|
||||||
|
super(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1002,15 +1002,22 @@ export function main() {
|
||||||
|
|
||||||
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(() => {
|
||||||
var ctx = createCompFixture('<div [someProp]="a"></div>', TestData);
|
const ctx = createCompFixture('<div [someProp]="a"></div>', TestData);
|
||||||
|
|
||||||
ctx.componentInstance.a = 1;
|
ctx.componentInstance.a = 1;
|
||||||
expect(() => ctx.checkNoChanges())
|
expect(() => ctx.checkNoChanges())
|
||||||
.toThrowError(/:0:5[\s\S]*Expression has changed after it was checked./g);
|
.toThrowError(/:0:5[\s\S]*Expression has changed after it was checked./g);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should warn when the view has been created in a cd hook', fakeAsync(() => {
|
||||||
|
const ctx = createCompFixture('<div *gh9882>{{ a }}</div>', TestData);
|
||||||
|
ctx.componentInstance.a = 1;
|
||||||
|
expect(() => ctx.detectChanges())
|
||||||
|
.toThrowError(
|
||||||
|
/It seems like the view has been created after its parent and its children have been dirty checked/);
|
||||||
|
}));
|
||||||
|
|
||||||
it('should not throw when two arrays are structurally the same', fakeAsync(() => {
|
it('should not throw when two arrays are structurally the same', fakeAsync(() => {
|
||||||
var ctx = _bindSimpleValue('a', TestData);
|
const ctx = _bindSimpleValue('a', TestData);
|
||||||
ctx.componentInstance.a = ['value'];
|
ctx.componentInstance.a = ['value'];
|
||||||
ctx.detectChanges(false);
|
ctx.detectChanges(false);
|
||||||
ctx.componentInstance.a = ['value'];
|
ctx.componentInstance.a = ['value'];
|
||||||
|
@ -1018,9 +1025,8 @@ export function main() {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should not break the next run', fakeAsync(() => {
|
it('should not break the next run', fakeAsync(() => {
|
||||||
var ctx = _bindSimpleValue('a', TestData);
|
const ctx = _bindSimpleValue('a', TestData);
|
||||||
ctx.componentInstance.a = 'value';
|
ctx.componentInstance.a = 'value';
|
||||||
|
|
||||||
expect(() => ctx.checkNoChanges()).toThrow();
|
expect(() => ctx.checkNoChanges()).toThrow();
|
||||||
|
|
||||||
ctx.detectChanges();
|
ctx.detectChanges();
|
||||||
|
@ -1093,17 +1099,28 @@ export function main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ALL_DIRECTIVES = /*@ts2dart_const*/[
|
const ALL_DIRECTIVES = /*@ts2dart_const*/[
|
||||||
forwardRef(() => TestDirective), forwardRef(() => TestComponent),
|
forwardRef(() => TestDirective),
|
||||||
forwardRef(() => AnotherComponent), forwardRef(() => TestLocals), forwardRef(() => CompWithRef),
|
forwardRef(() => TestComponent),
|
||||||
forwardRef(() => EmitterDirective), forwardRef(() => PushComp),
|
forwardRef(() => AnotherComponent),
|
||||||
forwardRef(() => OrderCheckDirective2), forwardRef(() => OrderCheckDirective0),
|
forwardRef(() => TestLocals),
|
||||||
forwardRef(() => OrderCheckDirective1), NgFor
|
forwardRef(() => CompWithRef),
|
||||||
|
forwardRef(() => EmitterDirective),
|
||||||
|
forwardRef(() => PushComp),
|
||||||
|
forwardRef(() => OrderCheckDirective2),
|
||||||
|
forwardRef(() => OrderCheckDirective0),
|
||||||
|
forwardRef(() => OrderCheckDirective1),
|
||||||
|
forwardRef(() => Gh9882),
|
||||||
|
NgFor,
|
||||||
];
|
];
|
||||||
|
|
||||||
const ALL_PIPES = /*@ts2dart_const*/[
|
const ALL_PIPES = /*@ts2dart_const*/[
|
||||||
forwardRef(() => CountingPipe), forwardRef(() => CountingImpurePipe),
|
forwardRef(() => CountingPipe),
|
||||||
forwardRef(() => MultiArgPipe), forwardRef(() => PipeWithOnDestroy),
|
forwardRef(() => CountingImpurePipe),
|
||||||
forwardRef(() => IdentityPipe), forwardRef(() => WrappedPipe), AsyncPipe
|
forwardRef(() => MultiArgPipe),
|
||||||
|
forwardRef(() => PipeWithOnDestroy),
|
||||||
|
forwardRef(() => IdentityPipe),
|
||||||
|
forwardRef(() => WrappedPipe),
|
||||||
|
AsyncPipe,
|
||||||
];
|
];
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -1259,7 +1276,7 @@ class EmitterDirective {
|
||||||
@Output('event') emitter = new EventEmitter<string>();
|
@Output('event') emitter = new EventEmitter<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Directive({selector: '[gh-9882]'})
|
@Directive({selector: '[gh9882]'})
|
||||||
class Gh9882 implements AfterContentInit {
|
class Gh9882 implements AfterContentInit {
|
||||||
constructor(private _viewContainer: ViewContainerRef, private _templateRef: TemplateRef<Object>) {
|
constructor(private _viewContainer: ViewContainerRef, private _templateRef: TemplateRef<Object>) {
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue