【问题标题】:Angular 4 ExpressionChangedAfterItHasBeenCheckedErrorAngular 4 ExpressionChangedAfterItHasBeenCheckedError
【发布时间】:2017-11-25 06:45:07
【问题描述】:

在父组件中 =>

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

【问题讨论】:

    标签: angular typescript angular-components


    【解决方案1】:

    文章Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedError error 非常详细地解释了这种行为

    原因

    您的问题与to this one 非常相似,但不是通过服务更新父属性,而是通过同步事件广播来更新它。这是linked answer的引述:

    在摘要周期中,Angular 对子节点执行某些操作 指令。其中一项操作是更新输入并调用 ngOnInit 生命周期挂钩子指令/组件。什么是 重要的是这些操作是按照严格的顺序执行的:

    • 更新输入
    • 调用 ngOnInit

    因此,在您的情况下,Angular 更新了子组件上的输入绑定 allItems,然后在子组件上调用 onInit,导致父组件的 allItems 更新。现在你有数据不一致。父组件具有一个值,而子组件具有另一个值。如果 Angular 继续同步更改,您将获得一个无限循环。这就是为什么在下一个更改检测周期中,Angular 检测到 allItems 已更改并引发错误。

    解决方案

    这似乎是一个应用程序设计缺陷,因为您正在从父组件和子组件更新details。如果不是,那么您可以通过像这样异步发出事件来解决问题:

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

    但是你必须非常小心。如果您使用在每个摘要循环中调用的任何其他钩子(例如 ngAfterViewChecked),您将最终陷入循环依赖!

    【讨论】:

    • 异步事件广播能解决这个问题吗?反正我可以做异步事件广播吗?
    • 只需使用setTimeout,我更新了问题。但问题实际上是您正在更新父子节点中的 allItems,这是您的应用程序设计缺陷,请考虑如何重新设计它
    • new EventEmitter(true)
    • @yurzui,刚刚检查,它使用setTimeout under the hood。它将安排一个宏任务。我想知道是否有任何方法可以安排微任务。我尝试使用promise.resolve,因为它安排了一个微任务,但错误仍然存​​在,需要调查
    【解决方案2】:

    在我的例子中,我正在改变我的数据状态——这个答案需要你阅读 AngularInDepth.com 对摘要周期的解释——在 html 级别中,我所要做的就是改变我处理数据的方式数据如下:

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

    进入

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

    总结:而不是弹出——它返回并删除数组的最后一个元素,从而改变了我的数据状态——我使用了正常的方式来获取我的数据而不改变其状态因此防止异常。

    【讨论】:

    • 两者不等价。一个返回最后一个,另一个返回第一个。但重点。
    【解决方案3】:

    收到此错误时,您可以使用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)
          })
        })
      }
    }
    

    【讨论】:

      猜你喜欢
      • 2018-01-20
      • 1970-01-01
      • 1970-01-01
      • 2017-10-19
      • 2018-04-17
      • 2021-01-02
      • 2017-12-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多