简体   繁体   English

Angular 4 ExpressionChangedAfterItHasBeenCheckedError

[英]Angular 4 ExpressionChangedAfterItHasBeenCheckedError

in 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)

Parent component Html父组件 Html

<div>
  <app-child-widget [allItems]="allItems" (notify)="eventCalled($event)"></app-child-widget>
<div>

Parent component父组件

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

Child component子组件

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

The article Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedError error explains this behavior in great details文章关于ExpressionChangedAfterItHasBeenCheckedError错误你需要知道的一切都非常详细地解释了这种行为

Cause原因

Your problem is very similar to this one but instead of updating parent property through a service you're updating it through synchronous event broadcasting.您的问题与问题非常相似但不是通过服务更新父属性,而是通过同步事件广播更新它。 Here is the quote from the linked answer :这是链接答案中的引用:

During digest cycle Angular performs certain operations on child directives.在摘要循环期间,Angular 会对子指令执行某些操作。 One of such operations is updating inputs and calling ngOnInit lifecycle hook on child directives/components.其中一项操作是更新输入并调用子指令/组件上的 ngOnInit 生命周期钩子。 What's important is that these operations are performed in strict order:重要的是这些操作是按照严格的顺序执行的:

  • Update inputs更新输入
  • Call ngOnInit调用 ngOnInit

So in your case Angular updated input binding allItems on child component, then called onInit on child component which caused an update to allItems of parent component.因此,在您的情况下,Angular 更新了子组件上的输入绑定allItems ,然后在子组件上调用了onInit ,这导致了对父组件的allItems的更新。 Now you have data inconsistency.现在你有数据不一致。 Parent component has one value while the child another.父组件有一个值,而子组件有另一个值。 If Angular continues synchronizing changes you'll get an infinite loop.如果 Angular 继续同步更改,您将获得无限循环。 That's why during next change detection cycle Angular detected that allItems was changed and thrown an error.这就是为什么在下一个更改检测周期 Angular 检测到allItems已更改并引发错误的原因。

Solution解决方案

It seems that this is an application design flaw as you're updating details from both parent and child component.这似乎是一个应用程序设计缺陷,因为您正在更新父组件和子组件的details If it's not, then you can solve the problem by emitting the event asynchronously like this:如果不是,那么您可以通过像这样异步发出事件来解决问题:

export class ChildComponent implements OnInit {
  @Output() notify: EventEmitter<any> = new EventEmitter(true);
                                                        ^^^^^^-------------

But you have to be very careful.但是你必须非常小心。 If you use any other hook like ngAfterViewChecked that is being called on every digest cycle, you'll end up in cyclic dependency!如果您使用任何其他钩子,如在每个摘要循环中调用的ngAfterViewChecked您最终将陷入循环依赖!

In my case, I was changing the state of my data--this answer requires you to read AngularInDepth.com explanation of digest cycle -- inside the html level, all I had to do was change the way I was handling the data like so:就我而言,我正在更改数据的状态——这个答案要求你阅读 AngularInDepth.com 对摘要循环的解释——在 html 级别内,我所要做的就是像这样改变我处理数据的方式:

<div>{{event.subjects.pop()}}</div>

into进入

<div>{{event.subjects[0]}}</div>

Summary: instead of popping--which returns and remove the last element of an array thus changing the state of my data-- I used the normal way of getting my data without changing its state thus preventing the exception.总结:不是弹出——它返回并删除数组的最后一个元素从而改变我的数据的状态——我使用了正常的方式来获取我的数据而不改变它的状态从而防止异常。

When Getting this error, you can use a rxjs timer to delay the call for a short time.当出现此错误时,您可以使用rxjs timer将调用延迟一小段时间。

This example uses the @angular/material stepper to load a default step from the query params in the url.此示例使用@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)
      })
    })
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM