Expression has changed after it was checked:如何解决 Angular 中的错误

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

「Expression has changed after it was checked」—— 为什么会发生以及如何应对?

\n\n

如果您曾经用 Angular 编写过应用程序,那么您一定在控制台中遇到过这个可怕的消息:

\n\n
ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'false'. Current value: 'true'.
\n\n

这个错误是开发者的真正「噩梦」。Stack Overflow 上有成千上万个问题,数百小时的调试,数十次精神崩溃。但好消息是:您不仅可以修复它,还可以理解它发生的原因。在本文中,我们将深入分析 Expression has changed after it was checked 错误的主要原因,用 TypeScript 编写示例,并提供具体的解决方案。开始吧!

\n\n

这个错误到底意味着什么?

\n\n

Angular 在两种模式下运行:开发模式(dev mode)和生产模式。在开发模式下,框架会执行额外的检查——在每个变更检测周期之后,它会运行第二次遍历,以确保数据没有「实时」改变。如果值在第一次和第二次遍历之间发生了变化——Angular 就会抛出 ExpressionChangedAfterItHasBeenCheckedError 错误。

\n\n

简单来说:您告诉 Angular「数据是这样的」,它检查了,然后您又更改了数据。Angular 会感到不满并报错。

\n\n

错误的三大主要原因

\n\n

1. 在 ngAfterViewInit 之后的生命周期钩子中更改数据

\n\n

最常见的原因——在 ngAfterViewInit、ngAfterContentInit 或 ngAfterViewChecked 钩子中尝试更改组件的属性。此时 Angular 已经完成了视图检查,任何更改都会引发错误。

\n\n

TypeScript 示例:

\n\n
import { Component, AfterViewInit } from '@angular/core';\n\n@Component({\n  selector: 'app-example',\n  template: '<p>{{ message }}</p>'\n})\nexport class ExampleComponent implements AfterViewInit {\n  message = '初始值';\n\n  ngAfterViewInit(): void {\n    // 错误!在视图检查后更改属性\n    this.message = '更改后的值';\n  }\n}
\n\n

这里 Angular 首先检查 message(值为「初始值」),然后调用 ngAfterViewInit,您在钩子中将其更改为「更改后的值」。在第二次检查时,Angular 发现不一致并抛出错误。

\n\n

2. 生命周期内的异步操作

\n\n

如果您在钩子中启动异步代码(setTimeout、Promise、Observable),则数据更新发生在 Angular 控制区域之外。

\n\n

示例:

\n\n
import { Component, OnInit } from '@angular/core';\n\n@Component({\n  selector: 'app-async',\n  template: '<p>{{ data }}</p>'\n})\nexport class AsyncComponent implements OnInit {\n  data = '开始';\n\n  ngOnInit(): void {\n    setTimeout(() => {\n      this.data = '新值';\n    }, 0);\n  }\n}
\n\n

尽管延迟为 0 毫秒的 setTimeout 几乎立即执行,但 Angular 已经完成了检查。在开发模式下,这会导致错误。

\n\n

3. 嵌套组件和通过 Input 传递数据

\n\n

当父组件通过 @Input 向子组件传递数据,而子组件试图在其生命周期中更改这些数据时,就会发生冲突。

\n\n

示例:

\n\n
// 父组件\n@Component({\n  selector: 'app-parent',\n  template: '<app-child [value]="parentValue"></app-child>'\n})\nexport class ParentComponent {\n  parentValue = '原始值';\n}\n\n// 子组件\n@Component({\n  selector: 'app-child',\n  template: '<p>{{ value }}</p>'\n})\nexport class ChildComponent implements AfterViewInit {\n  @Input() value: string = '';\n\n  ngAfterViewInit(): void {\n    this.value = '在子组件中更改的值';\n  }\n}

博客

书籍推荐