在AfterViewInit中更新布尔值会导致"表达式在检查后发生了变化"

时间:2017-07-10 12:31:24

标签: angular angular2-changedetection

我有一个简单的警报组件,我在视图中动态创建。由于它是动态创建的,因此我设置了一个选项,可以在初始化后自动显示警报。

虽然它有效,但我想了解为什么在这种特殊情况下我必须手动触发变更检测。

代码:

export class OverlayMessageComponent implements AfterViewInit {
    ...

    ngAfterViewInit() {
        if(this.autoShow) {
            this.show();
        }
        this.changeDetector.detectChanges();
    }

    ...
}

完整示例: https://plnkr.co/edit/8NvfhDvLVBd71I7DR0kW

我必须添加this.changeDetector.detectChanges();,因为我收到以下错误:

  

EXCEPTION:检查后表情发生了变化。

我的印象是使用AfterViewInit有助于避免这个问题,但我认为我错了。有没有办法更好地构造代码以避免此错误?

我想更好地理解为什么会返回此错误。我之前已经看过几次这个错误了,我知道有人说用setTimeout()enableProdMode()确实可以解决这个问题,但对我而言,这个框架本身似乎是一个愚蠢的解决方法通知你有问题。

3 个答案:

答案 0 :(得分:3)

  

我想更好地理解为什么会返回此错误

在更改检测完成并构建视图后,将触发AfterViewInitAfterViewChecked生命周期挂钩。因此,此时运行的任何代码都不应更新视图,否则您的应用及其视图将不同步。拿一个look at the docs

  

Angular的单向数据流规则禁止在编写视图后对视图进行更新。组件的视图组合后,这两个钩子都会触发。

     

如果挂钩立即更新组件的数据绑定注释属性,则Angular会抛出错误。

因此,您必须手动触发更改检测 - 这是一项昂贵的操作,因为Angular必须再次通过整个应用程序 - 或者异步进行更改,以便在下次更改检测时更新视图步骤,例如:

if(this.autoShow) { setTimeout(()=>this.show,0)}

或者更简单地说,如果您不需要在视图中抓取某个内容的句柄,则可以在ngOnInit()或稍后ngAfterContentInit()中运行您的代码。因为它们是在编写视图之前运行的,所以您可以毫不费力地进行影响视图的更改。

文档:lifecycle hooks order

答案 1 :(得分:3)

修复

对于您的特定情况,无需触发更改检测或使用异步更新。修复很简单,只需将this.show移动到ngOnInit生命周期钩子:

  ngOnInit() {
    if(this.autoShow) {
      this.show();
    }
  }

解释

因为您在模板绑定中使用了bringIconToFront组件属性:

<div class="icon home" [class.add-z-index]="bringIconToFront"></div>

Angular应该更新App组件的DOM。此外,Angular调用子OverlayMessage组件的生命周期钩子。 DOM udpate和生命周期钩子按顺序执行here

  • 在子组件上调用OnInitngDoCheck(仅在首次检查时调用OnInit
  • 如果当前视图组件实例上的属性发生更改,则更新当前App视图的DOM插值和绑定。
  • 为子ngAfterViewInit组件
  • 调用ngAfterViewCheckedOverlayMessage
  • 为当前ngAfterViewInit组件
  • 调用ngAfterViewCheckedApp

您可以看到在为当前组件更新DOM绑定之前调用了onInit。然后调用ngAfterViewInit。这就是为什么它在一个案例中起作用而在另一个案例中不起作用的原因。

本文将帮助您更好地理解错误 - Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedError error

答案 2 :(得分:0)

在角度运行变更检测后更新模型时,或者如果更新未处于角度世界中,请使用detectChanges()