在 AfterViewInit 中更新布尔值会导致“检查后表达式已更改”

Posted

技术标签:

【中文标题】在 AfterViewInit 中更新布尔值会导致“检查后表达式已更改”【英文标题】:Updating boolean in AfterViewInit causes "Expression has changed after it was checked" 【发布时间】:2017-12-14 04:38:47 【问题描述】:

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

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

代码:

export class OverlayMessageComponent implements AfterViewInit 
    ...

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

    ...

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

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

异常:表达式在检查后发生了变化。

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

我想更好地了解为什么会返回此错误。我以前见过这个错误几次,我知道有人说setTimeout()enableProdMode() 确实解决了这个问题,但对我来说,当框架本身通知你有一个问题。

【问题讨论】:

这将有助于从这里了解detectChanges ***.com/a/41364469/8171406 本文将帮助您更好地理解错误-Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedError error。 【参考方案1】:

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

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

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

如果钩子立即更新组件的数据绑定评论属性,Angular 会抛出错误。

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

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

或者更简单地说,如果您不需要在视图中获取某些内容的句柄,您可以在ngOnInit() 或稍后在ngAfterContentInit() 中运行您的代码。因为它们在视图组合之前运行,所以您可以轻松地进行影响视图的更改。

文档:lifecycle hooks order

【讨论】:

@Maximus 你是什么意思? @Maximus 我还向底部建议了ngOnInit。取决于用户是否需要获取视图中的某些内容。 @Maximus 我不知道它是否在那里工作,因为我们只有 OP 中的摘录 我添加了信息,如果您移至ngOnInit,为什么它会起作用 这不会在setTimeout 触发后触发另一个changeDetection 吗?为什么它会比手动触发变更检测便宜?【参考方案2】:

修复

对于您的特定情况,无需触发更改检测或使用异步更新。修复很简单,只需将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 更新和生命周期钩子按照here 所示的顺序执行:

在子组件上调用 OnInitngDoCheckOnInit 仅在第一次检查时调用) 如果当前视图组件实例上的属性发生更改,则更新当前 App 视图的 DOM 插值和绑定` 为子 OverlayMessage 组件调用 ngAfterViewInitngAfterViewChecked 为当前的App 组件调用ngAfterViewInitngAfterViewChecked

您可以看到在为当前组件更新 DOM 绑定之前调用了 onInit。然后调用ngAfterViewInit。这就是为什么它在一种情况下有效而在另一种情况下无效的原因。

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

【讨论】:

感谢您的详细解答。这对我帮助很大! 不客气,您肯定会想阅读我在答案中提到的文章,以避免将来出现这些错误。我还参考了one of your answers下的一篇关于forwardRef的文章 @DanielGrima,谢谢,请务必关注我以获得更深入的文章)【参考方案3】:

当您在 Angular 运行后更新模型时,使用 detectChanges() 进行更改检测,或者如果更新根本不在 Angular 世界中。

【讨论】:

以上是关于在 AfterViewInit 中更新布尔值会导致“检查后表达式已更改”的主要内容,如果未能解决你的问题,请参考以下文章

[DataGridCheckBoxColumn在属性更改时不会在MVVM中更新

为啥@ViewChild 不能在 AfterViewInit 之外运行? [复制]

Jasmine 测试中 AfterViewInit 的生命周期钩子

在字段中查找重复值会导致多行相同的值

动画状态改变但不是实际动画

jquery.inputmask - 在“oncomplete”回调中设置值会导致不正确的操作