ExpressionChangedAfterItHasBeenCheckedError解释

Kev*_*rge 243 angular2-changedetection angular2-databinding angular

请向我解释为什么我一直收到这个错误: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.

显然,我只是在开发模式下获得它,它不会在我的生产版本中发生,但它非常烦人,而我根本不理解在我的开发环境中出现错误的好处 - 不会出现在prod上 - - 可能是因为我缺乏理解.

通常,修复很容易,我只是将错误导致代码包装在setTimeout中,如下所示:

setTimeout(()=> {
    this.isLoading = true;
}, 0);
Run Code Online (Sandbox Code Playgroud)

或者使用如下构造函数强制检测更改constructor(private cd: ChangeDetectorRef) {}::

this.isLoading = true;
this.cd.detectChanges();
Run Code Online (Sandbox Code Playgroud)

但为什么我经常遇到这个错误?我想了解它,以便将来可以避免这些hacky修复.

小智 96

我有类似的问题.看一下生命周期钩子文档,我改变ngAfterViewInitngAfterContentInit它并且它起作用了.

  • @PhilipEnc 我的问题与 DOM 更改触发的更改有关。当 DOM 更改时,QueryList 对象(来自 @ContentChildren 属性)将更新,并且在更新调用它的方法内部更改了双向绑定属性。这造成了我遇到的问题。像上面展示的那样,使用 `setTimeout` 将该更改包装到两个属性中就可以了。谢谢! (2认同)
  • `ngAfterContentChecked` 在这里工作,而 `ngAfterContentInit` 仍然抛出错误。 (2认同)

Gün*_*uer 84

此错误表示应用程序中存在实际问题,因此抛出异常是有意义的.

devMode更改检测中,在每次常规更改检测运行后添加一个额外的转弯,以检查模型是否已更改.

如果模型在常规和附加变化检测转弯之间发生了变化,则表明这两者之一

  • 变化检测本身已经引起了变化
  • 每次调用时,方法或getter都会返回不同的值

哪些都不好,因为不清楚如何继续,因为模型可能永远不会稳定.

如果Angular运行更改检测,直到模型稳定,它可能会永远运行.如果Angular未运行更改检测,则视图可能不会反映模型的当前状态.

另请参阅Angular2中的生产和开发模式有何区别?

  • 通常这是由一些生命周期回调引起的,例如`ngOnInit`或`ngOnChanges`来修改模型(一些生命周期回调允许修改其他人没有的模型,我不记得自己究竟是哪一个做或不做).不要绑定到视图中的方法或函数,而是绑定到字段并更新事件处理程序中的字段.如果必须绑定到方法,请确保它们始终返回相同的值实例,只要实际没有更改即可.变更检测会调用这些方法. (22认同)
  • 我怎样才能避免将来看到这个错误?有没有不同的方式我需要考虑我的代码,以确保我不犯同样的错误? (4认同)
  • 应用程序不一定有问题。事实证明,调用`changeRef.detectChanges()`是一种解决方案/可以抑制错误。这就像在Angular 1中的$ scope。$ watch()`中修改状态一样。 (2认同)

Sha*_*asi 66

我正在使用ng2-carouselamos(Angular 8 和 Bootstrap 4)

采取这些步骤解决了我的问题:

  1. 实施 AfterViewChecked
  2. 添加 constructor(private changeDetector : ChangeDetectorRef ) {}
  3. 然后 ngAfterViewChecked(){ this.changeDetector.detectChanges(); }


Kev*_*rge 63

一旦我理解了Angular Lifecycle Hooks及其与变化检测的关系,就会有很多理解.

我试图让Angular更新绑定到*ngIf元素的全局标志,并且我试图在ngOnInit()另一个组件的生命周期钩子内部更改该标志.

根据文档,在Angular已经检测到更改后调用此方法:

在第一次ngOnChanges()之后调用一次.

因此更新内部标志ngOnChanges()不会启动更改检测.然后,一旦更改检测自然再次触发,则标志的值已更改并且引发错误.

在我的情况下,我改变了这个:

constructor(private globalEventsService: GlobalEventsService) {

}

ngOnInit() {
    this.globalEventsService.showCheckoutHeader = true;
}
Run Code Online (Sandbox Code Playgroud)

对此:

constructor(private globalEventsService: GlobalEventsService) {
    this.globalEventsService.showCheckoutHeader = true;
}

ngOnInit() {

}
Run Code Online (Sandbox Code Playgroud)

它解决了问题:)

  • 我的问题是相似的。长时间工作后我犯了一个错误,并在ngOnInit函数和构造函数之外定义了一个变量。它从可观察对象接收数据更改,可观察对象放置在初始化函数中。与您进行了相同的操作来纠正错误。 (3认同)

Arn*_*d P 36

更新

我强烈建议首先从OP的自我回应开始:正确思考在constructorvs应该做什么可以做什么ngOnChanges().

原版的

这是一个侧面说明而不是答案,但它可能对某人有所帮助.当我试图使按钮的存在取决于表单的状态时,我偶然发现了这个问题:

<button *ngIf="form.pristine">Yo</button>
Run Code Online (Sandbox Code Playgroud)

据我所知,这种语法导致根据条件添加和删除DOM按钮.这反过来导致了ExpressionChangedAfterItHasBeenCheckedError.

在我的情况下修复(虽然我没有声称要掌握差异的全部含义),display: none而是使用:

<button [style.display]="form.pristine ? 'inline' : 'none'">Yo</button>
Run Code Online (Sandbox Code Playgroud)

  • 我对ngIf与样式之间差异的理解是,在条件为真之前,ngIf不包含页面中的HTML,因此在样式技术导致HTML始终为时,"页面权重"减少了一点点在页面中,只是隐藏或显示基于form.pristine的值. (6认同)
  • 您也可以使用`[hidden]`而不是非常详细的`[style.display]`部分.:) (4认同)
  • 为什么不。尽管,如@Simon_Weaver在此页面上的另一条评论中所述,`[隐藏] [不会总是具有相同的行为](http://www.talkingdotnet.com/dont-use-hidden-attribute-angularjs-2 /)作为“显示:无” (2认同)

epe*_*per 23

有一些有趣的答案,但我似乎找不到符合我需要的答案,最接近的是来自@ chittrang-mishra,它只涉及一个特定的功能而不是我的应用程序中的几个切换.

我不想使用[hidden]*ngIf甚至不作为DOM的一部分,所以我发现下面的解决方案,它可能不是最好的一切,因为它抑制了错误,而不是试图解决这个问题,但对我来说,我知道最终结果是正确的,我的应用程序似乎没问题.

我所做的是实现AfterViewChecked,添加constructor(private changeDetector : ChangeDetectorRef ) {}然后

ngAfterViewChecked(){
  this.changeDetector.detectChanges();
}
Run Code Online (Sandbox Code Playgroud)

我希望这有助于其他许多人帮助我.

  • 这不会触发无限变化检测循环吗?我的意思是,检查后您正在检测更改。 (2认同)
  • @ManuelAzar 不存在无限循环,因为 `ngAfterViewChecked` 在组件生命周期中仅被调用一次。触发更改检测周期不会再次启动组件生命周期,因此除非从头开始重新渲染组件,否则不会再次调用“ngAfterViewChecked”。(请注意,其他一些挂钩,例如“ngDoCheck”,将由额外的更改检测周期触发,因为它们会误导性地挂钩到更改检测周期而不是直接挂钩到组件生命周期。在“ngDoCheck”中使用“detectChanges()”将导致无限循环。) (2认同)

And*_*sta 20

在我的情况下,我在我的spec文件中遇到了这个问题,同时运行我的测试.

我不得不换ngIf [hidden]

<app-loading *ngIf="isLoading"></app-loading>
Run Code Online (Sandbox Code Playgroud)

<app-loading [hidden]="!isLoading"></app-loading>
Run Code Online (Sandbox Code Playgroud)

  • 这里的区别在于,`*ngIf` 更改了 DOM,在页面中添加和删除元素,而 `[hidden]` 更改项目的可见性,而不是从 DOM 中删除它。 (2认同)
  • 但是,这并不能真正解决真正的问题...? (2认同)

Chi*_*hra 19

请按照以下步骤操作:

1.使用'ChangeDetectorRef'从@ angular/core导入它,如下所示:

import{ ChangeDetectorRef } from '@angular/core';
Run Code Online (Sandbox Code Playgroud)

2.在构造函数()中实现它,如下所示:

constructor(   private cdRef : ChangeDetectorRef  ) {}
Run Code Online (Sandbox Code Playgroud)

3.将以下方法添加到您正在调用单击按钮等事件的函数中.所以它看起来像这样:

functionName() {   
    yourCode;  
    //add this line to get rid of the error  
    this.cdRef.detectChanges();     
}
Run Code Online (Sandbox Code Playgroud)


小智 16

我遇到了同样的问题,因为我的组件中的一个数组中的值发生了变化.但是,我没有检测到值变化的变化,而是将组件变化检测策略更改为onPush(它将检测对象更改的更改而不是值更改).

import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';

@Component({
    changeDetection: ChangeDetectionStrategy.OnPush
    selector: -
    ......
})
Run Code Online (Sandbox Code Playgroud)


Lij*_*ijo 14

Angular运行更改检测,当它发现已传递给子组件的某些值已更改时,Angular会抛出错误ExpressionChangedAfterItHasBeenCheckedError 单击"更多"

为了纠正这个问题,我们可以使用AfterContentChecked生命周期钩子和

import { ChangeDetectorRef, AfterContentChecked} from '@angular/core';

  constructor(
  private cdref: ChangeDetectorRef) { }

  ngAfterContentChecked() {

    this.cdref.detectChanges();

  }
Run Code Online (Sandbox Code Playgroud)

  • @Nicky 是的。每次您触摸屏幕上的任何位置时,都会调用 ngAfterContentChecked() (4认同)
  • 虽然这可能会解决这个问题,但这不是非常广泛和过度杀伤 CD 吗? (2认同)

ATH*_*HER 13

参考文章https://blog.angularindepth.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4

因此,变更检测背后的机制实际上是以同步执行变更检测和验证摘要的方式工作的.这意味着,如果我们异步更新属性,则在验证循环运行时不会更新值,我们也不会收到ExpressionChanged...错误.我们得到此错误的原因是,在验证过程中,Angular看到的值与更改检测阶段记录的值不同.所以要避免......

1)使用changeDetectorRef

2)使用setTimeOut.这将作为宏任务在另一个VM中执行您的代码.Angular在验证过程中不会看到这些更改,您将不会收到该错误.

 setTimeout(() => {
        this.isLoading = true;
    });
Run Code Online (Sandbox Code Playgroud)

3)如果你真的想在同一个VM上执行代码,请使用

Promise.resolve(null).then(() => this.isLoading = true);
Run Code Online (Sandbox Code Playgroud)

这将创建一个微任务.在当前同步代码完成执行之后处理微任务队列,因此在验证步骤之后将对属性进行更新.


ted*_*edw 10

ExpressionChangedAfterItHasBeenCheckedError如果您在触发EventEmitter内部时看到ngAfterViewInit(),那么您可以true在创建时传递,EventEmitter以使其在当前更改检测周期后异步发出。

@Component({
  ...
})
export class MyComponent implements AfterViewInit {
  // Emit asynchronously to avoid ExpressionChangedAfterItHasBeenCheckedError
  @Output() valueChange = new EventEmitter<number>(true);

  ...

  ngAfterViewInit(): void {
    ...
    this.valueChange.emit(newValue);
    ...
  }

}
Run Code Online (Sandbox Code Playgroud)


小智 7

这是我对正在发生的事情的想法。我还没有阅读文档,但确信这是显示错误的部分原因。

*ngIf="isProcessing()" 
Run Code Online (Sandbox Code Playgroud)

当使用 *ngIf 时,每次条件发生变化时,它都会通过添加或删除元素来物理更改 DOM。因此,如果条件在渲染到视图之前发生变化(这在 Angular 的世界中很有可能),则会抛出错误。请参阅此处有关开发和生产模式的说明。

[hidden]="isProcessing()"
Run Code Online (Sandbox Code Playgroud)

使用时[hidden]它不会物理改变,而只是从视图中DOM隐藏,最有可能在后面使用。该元素仍然存在于 DOM 中,但根据条件的值不可见。这就是为什么使用时不会出现错误。elementCSS[hidden]


Sim*_*ver 6

@HostBinding 可能是此错误的一个令人困惑的来源。

例如,假设您在组件中具有以下主机绑定

// image-carousel.component.ts
@HostBinding('style.background') 
style_groupBG: string;
Run Code Online (Sandbox Code Playgroud)

为简单起见,假设此属性是通过以下输入属性更新的:

@Input('carouselConfig')
public set carouselConfig(carouselConfig: string) 
{
    this.style_groupBG = carouselConfig.bgColor;   
}
Run Code Online (Sandbox Code Playgroud)

在父组件中,您以编程方式将其设置在 ngAfterViewInit

@ViewChild(ImageCarousel) carousel: ImageCarousel;

ngAfterViewInit()
{
    this.carousel.carouselConfig = { bgColor: 'red' };
}
Run Code Online (Sandbox Code Playgroud)

这是发生的事情:

  • 您的父组件已创建
  • ImageCarousel 组件被创建,并分配给carousel(通过 ViewChild)
  • 我们无法访问carousel直到ngAfterViewInit()(它将为空)
  • 我们分配配置,它设置 style_groupBG = 'red'
  • 这反过来设置background: red主机 ImageCarousel 组件
  • 此组件由您的父组件“拥有”,因此当它检查更改时,它会发现更改carousel.style.background并且不够聪明,无法知道这不是问题,因此会引发异常。

一种解决方案是引入另一个包装器 div 内幕 ImageCarousel 并在其上设置背景颜色,但随后您无法获得使用的一些好处HostBinding(例如允许父级控制对象的完整边界)。

更好的解决方案,在父组件中设置config后添加detectChanges()。

ngAfterViewInit()
{
    this.carousel.carouselConfig = { ... };
    this.cdr.detectChanges();
}
Run Code Online (Sandbox Code Playgroud)

这可能看起来很明显,并且与其他答案非常相似,但存在细微差别。

考虑@HostBinding在开发过程中稍后才添加的情况。突然你得到这个错误,它似乎没有任何意义。


Sim*_*ver 6

调试技巧

这个错误可能非常令人困惑,并且很容易对它发生的确切时间做出错误的假设。我发现在受影响的组件中的适当位置添加大量这样的调试语句很有帮助。这有助于理解流程。

在父级 put 语句中,像这样(确切的字符串“EXPRESSIONCHANGED”很重要),但除此之外,这些只是示例:

    console.log('EXPRESSIONCHANGED - HomePageComponent: constructor');
    console.log('EXPRESSIONCHANGED - HomePageComponent: setting config', newConfig);
    console.log('EXPRESSIONCHANGED - HomePageComponent: setting config ok');
    console.log('EXPRESSIONCHANGED - HomePageComponent: running detectchanges');
Run Code Online (Sandbox Code Playgroud)

在子/服务/计时器回调中:

    console.log('EXPRESSIONCHANGED - ChildComponent: setting config');
    console.log('EXPRESSIONCHANGED - ChildComponent: setting config ok');
Run Code Online (Sandbox Code Playgroud)

如果您detectChanges也手动运行添加日志记录:

    console.log('EXPRESSIONCHANGED - ChildComponent: running detectchanges');
    this.cdr.detectChanges();
Run Code Online (Sandbox Code Playgroud)

然后在 Chrome 调试器中只需按“EXPRESSIONCHANGES”进行过滤。这将准确地向您显示所设置的所有内容的流程和顺序,以及 Angular 在什么时候抛出错误。

在此输入图像描述

您还可以单击灰色链接来放置断点。

另一件需要注意的事情是,如果您的应用程序中具有类似命名的属性(例如style.background),请确保您正在调试您认为的属性 - 通过将其设置为模糊的颜色值。


gok*_*ter 5

当我添加时我的问题很明显,*ngIf但这不是原因。该错误是由于更改标签中的模型{{}}然后尝试*ngIf稍后在语句中显示更改后的模型而引起的。这是一个例子:

<div>{{changeMyModelValue()}}</div> <!--don't do this!  or you could get error: ExpressionChangedAfterItHasBeenCheckedError-->
....
<div *ngIf="true">{{myModel.value}}</div>
Run Code Online (Sandbox Code Playgroud)

为了解决这个问题,我将通话地点更改changeMyModelValue()为更有意义的地方。

changeMyModelValue()在我的情况下,每当子组件更改数据时我都想调用。这需要我在子组件中创建并发出一个事件,以便父组件可以处理它(通过调用changeMyModelValue(). 请参阅https://angular.io/guide/component-interaction#parent-listens-for-child-event


Saf*_*lai 5

尝试了上面建议的大多数解决方案。在这种情况下,只有这对我有用。我正在使用 *ngIf 根据 api 调用切换角度材料的不确定进度条,它正在抛出ExpressionChangedAfterItHasBeenCheckedError.

在相关组件中:

constructor(
    private ngZone: NgZone,
    private changeDetectorRef: ChangeDetectorRef,
) {}

ngOnInit() {
    this.ngZone.runOutsideAngular(() => {
        this.appService.appLoader$.subscribe(value => {
            this.loading = value;
            this.changeDetectorRef.detectChanges();
        });
    });
}
Run Code Online (Sandbox Code Playgroud)

诀窍是使用 ngzone 绕过角度组件的变化检测。

PS:不确定这是否是一个优雅的解决方案,但使用 AfterContentChecked 和 AfterViewChecked 生命周期挂钩必然会引发性能问题,因为您的应用程序会因多次触发而变得越来越大。


Jus*_*ger 5

添加static: true@ViewChild装饰器

@ViewChild(UploadComponent, { static: true }) uploadViewChild: UploadComponent;
Run Code Online (Sandbox Code Playgroud)