Angular 4 ExpressionChangedAfterItHasBeenCheckedError
Angular 4 ExpressionChangedAfterItHasBeenCheckedError
在 ParentComponent =>
ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: ''. Current value: '[object Object]'.
at viewDebugError (vendor.bundle.js:8962)
at expressionChangedAfterItHasBeenCheckedError (vendor.bundle.js:8940)
父组件Html
<div>
<app-child-widget [allItems]="allItems" (notify)="eventCalled($event)"></app-child-widget>
<div>
父组件
export class ParentComponent implements OnInit {
returnedItems: Array<any> = [];
allItems: Array<any> = [];
constructor(
) { }
ngOnInit() {
this.allItems = // load from server...
}
eventCalled(items: Array<any>): void {
this.returnedItems = items;
}
}
子组件
@Component({
selector: 'app-child-widget',
templateUrl: 'child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit {
@Output() notify: EventEmitter<any> = new EventEmitter();
@Input() private allItems: Array<any>;
constructor() { }
ngOnInit() {
doSomething();
}
doSomething() {
this.notify.emit(allItems);
}
}
文章 Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedError
error 详细解释了此行为
原因
你的问题很相似 but instead of updating parent property through a service you're updating it through synchronous event broadcasting. Here is the quote from the :
During digest cycle Angular performs certain operations on child
directives. One of such operations is updating inputs and calling
ngOnInit lifecycle hook on child directives/components. What's
important is that these operations are performed in strict order:
- Update inputs
- Call ngOnInit
因此,在您的情况下,Angular 更新了子组件上的输入绑定 allItems
,然后在子组件上调用了 onInit
,这导致对父组件的 allItems
进行了更新。现在你有数据不一致。父组件有一个值,而子组件有另一个值。如果 Angular 继续同步更改,您将陷入无限循环。这就是为什么在下一个更改检测周期中 Angular 检测到 allItems
已更改并引发错误。
解决方案
这似乎是一个应用程序设计缺陷,因为您要从父组件和子组件更新 details
。如果不是,那么您可以像这样异步发出事件来解决问题:
export class ChildComponent implements OnInit {
@Output() notify: EventEmitter<any> = new EventEmitter(true);
^^^^^^-------------
但是你必须非常小心。如果您使用在每个摘要循环中调用的任何其他钩子,例如 ngAfterViewChecked
,您最终将陷入循环依赖!
在我的例子中,我正在改变我的数据状态——这个答案需要你阅读 AngularInDepth.com 摘要周期的解释——在 html 级别,我必须做的一切正在改变我处理数据的方式:
<div>{{event.subjects.pop()}}</div>
进入
<div>{{event.subjects[0]}}</div>
总结:而不是弹出——returns 并删除数组的最后一个元素从而改变我的数据的状态——我使用正常的方式获取我的 数据而不改变它的状态从而防止异常。
当出现这个错误时,可以使用rxjs
timer
来短时间延迟调用。
此示例使用 @angular/material
stepper
从 url.
中的查询参数加载默认步骤
import { timer } from 'rxjs'
@Component({
template: `
<mat-horizontal-stepper #stepper>
<mat-step></mat-step>
<mat-step></mat-step>
<mat-step></mat-step>
</mat-horizontal-stepper>
`
})
export class MyComponent {
@ViewChild('stepper', { static: true })
private stepper: MatHorizontalStepper
public ngOnInit() {
timer(10).subscribe(() => {
this.activatedRoute.queryParams.subscribe(params => {
this.stepper.selectedIndex = parseInt(params.step || 0)
})
})
}
}
在 ParentComponent =>
ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: ''. Current value: '[object Object]'.
at viewDebugError (vendor.bundle.js:8962)
at expressionChangedAfterItHasBeenCheckedError (vendor.bundle.js:8940)
父组件Html
<div>
<app-child-widget [allItems]="allItems" (notify)="eventCalled($event)"></app-child-widget>
<div>
父组件
export class ParentComponent implements OnInit {
returnedItems: Array<any> = [];
allItems: Array<any> = [];
constructor(
) { }
ngOnInit() {
this.allItems = // load from server...
}
eventCalled(items: Array<any>): void {
this.returnedItems = items;
}
}
子组件
@Component({
selector: 'app-child-widget',
templateUrl: 'child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit {
@Output() notify: EventEmitter<any> = new EventEmitter();
@Input() private allItems: Array<any>;
constructor() { }
ngOnInit() {
doSomething();
}
doSomething() {
this.notify.emit(allItems);
}
}
文章 Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedError
error 详细解释了此行为
原因
你的问题很相似
During digest cycle Angular performs certain operations on child directives. One of such operations is updating inputs and calling ngOnInit lifecycle hook on child directives/components. What's important is that these operations are performed in strict order:
- Update inputs
- Call ngOnInit
因此,在您的情况下,Angular 更新了子组件上的输入绑定 allItems
,然后在子组件上调用了 onInit
,这导致对父组件的 allItems
进行了更新。现在你有数据不一致。父组件有一个值,而子组件有另一个值。如果 Angular 继续同步更改,您将陷入无限循环。这就是为什么在下一个更改检测周期中 Angular 检测到 allItems
已更改并引发错误。
解决方案
这似乎是一个应用程序设计缺陷,因为您要从父组件和子组件更新 details
。如果不是,那么您可以像这样异步发出事件来解决问题:
export class ChildComponent implements OnInit {
@Output() notify: EventEmitter<any> = new EventEmitter(true);
^^^^^^-------------
但是你必须非常小心。如果您使用在每个摘要循环中调用的任何其他钩子,例如 ngAfterViewChecked
,您最终将陷入循环依赖!
在我的例子中,我正在改变我的数据状态——这个答案需要你阅读 AngularInDepth.com 摘要周期的解释——在 html 级别,我必须做的一切正在改变我处理数据的方式:
<div>{{event.subjects.pop()}}</div>
进入
<div>{{event.subjects[0]}}</div>
总结:而不是弹出——returns 并删除数组的最后一个元素从而改变我的数据的状态——我使用正常的方式获取我的 数据而不改变它的状态从而防止异常。
当出现这个错误时,可以使用rxjs
timer
来短时间延迟调用。
此示例使用 @angular/material
stepper
从 url.
import { timer } from 'rxjs'
@Component({
template: `
<mat-horizontal-stepper #stepper>
<mat-step></mat-step>
<mat-step></mat-step>
<mat-step></mat-step>
</mat-horizontal-stepper>
`
})
export class MyComponent {
@ViewChild('stepper', { static: true })
private stepper: MatHorizontalStepper
public ngOnInit() {
timer(10).subscribe(() => {
this.activatedRoute.queryParams.subscribe(params => {
this.stepper.selectedIndex = parseInt(params.step || 0)
})
})
}
}