ExpressionChangedAfterItHasBeenCheckedError说明


307

请向我解释为什么我不断收到此错误: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.

显然,我只在开发人员模式下获得它,它在我的生产版本中不会发生,但是这很烦人,我根本不理解在我的开发人员环境中出现错误并不会在产品上显示出来的好处- -可能是由于我缺乏了解。

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

setTimeout(()=> {
    this.isLoading = true;
}, 0);

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

this.isLoading = true;
this.cd.detectChanges();

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


Answers:


121

我有一个类似的问题。查看生命周期挂钩文档,我更改ngAfterViewInit为,ngAfterContentInit并且它起作用了。


@PhilipEnc我的问题与DOM更改触发的更改有关。当DOM更改时,QueryList对象(来自@ContentChildren属性)将更新,并且在该更新调用的方法内部更改了双向绑定属性。这造成了我遇到的问题。setTimeout就像您在上面显示的那样,将更改包装为两个属性就可以了。谢谢!
kbpontius

1
在我的情况下,我将一些代码更改了primeng网格数组的值放在ngAfterContentInit中,将代码放置在ngOnInit中,并且它起作用了。
Vibhu

ngAfterContentChecked在这里工作,但ngAfterContentInit仍会引发错误。
ashubuntu

ngAfterContentChecked使用,但项目加载非常慢
Ghotekar Rahul

101

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

devMode变更检测中,每次常规变更检测运行后都会增加一圈,以检查模型是否已变更。

如果模型在常规和其他变化检测转弯之间发生了变化,则表明

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

两者都是不好的,因为由于模型可能永远无法稳定,因此不清楚如何进行。

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

另请参见Angular2中生产和开发模式之间的区别是什么?


4
如何避免以后再出现此错误?我需要考虑一下代码以确保不会犯同样的错误吗?
凯文·莱斯塔格'17

24
通常,这是由某些生命周期回调(例如ngOnInitngOnChanges修改模型)引起的(某些生命周期回调允许修改模型,而其他人则不能,我不记得自己到底该做什么或不做什么)。不要绑定到视图中的方法或函数,而是绑定到字段并在事件处理程序中更新字段。如果必须绑定到方法,请确保它们始终返回相同的值实例,只要实际上没有更改即可。变更检测将大量调用这些方法。
君特Zöchbauer

对于使用ngx-toaster库到达此错误的任何人,这里是错误报告:github.com/scttcper/ngx-toastr/issues/160
rmcsharry

2
应用程序不一定有问题。调用changeRef.detectChanges()是解决方案/抑制错误的事实就是对此的证明。这就像修改状态中$scope.$watch()的角1
凯文·比尔

1
我不太了解Angular 1,但是Angular 2中的更改检测的工作方式却大不相同。没错,这不一定是问题,但通常cdRef.detectChanges()仅在某些奇怪的极端情况下才是必需的,并且在需要时应仔细查看以正确理解其原因。
君特Zöchbauer

83

一旦我了解了Angular Lifecycle Hook及其与变更检测的关系,便有了很多理解。

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

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

在第一个ngOnChanges()之后调用一次。

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

就我而言,我对此进行了更改:

constructor(private globalEventsService: GlobalEventsService) {

}

ngOnInit() {
    this.globalEventsService.showCheckoutHeader = true;
}

对此:

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

ngOnInit() {

}

它解决了问题:)


3
我的问题是相似的。长时间工作后我犯了一个错误,并在ngOnInit函数和构造函数之外定义了一个变量。这从放置在初始化函数中的Observable接收数据更改。与您做相同的事情来纠正错误。
ravo10

1
周围都很相似,但是我试图router.navigate在加载时滚动()到URL中存在的片段。这段代码最初放置在AfterViewInit我纠正错误的位置,然后按照您所说的向构造函数移动,但它不遵守该片段。移动ngOnInit解决:)谢谢!
乔尔·巴尔默

如果我的html通过get ClockValue(){return DateTime.TimeAMPM(new Date())}绑定到getter返回时间为“ HH:MM”怎么办,它将在检测运行过程中更改分钟数时最终跳闸,怎么办解决这个问题?
Meryan

同样在这里。还发现,setInterval()如果需要在其他生命周期事件代码之后触发,则也可以将其包装到工程中。
Rick Strahl

39

更新资料

我强烈建议您首先从OP的自我回应入手:正确考虑constructorvs中应该做什么ngOnChanges()

原版的

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

<button *ngIf="form.pristine">Yo</button>

据我所知,此语法导致根据条件在DOM中添加和删除按钮。反过来导致ExpressionChangedAfterItHasBeenCheckedError

在我的情况下,解决方法是(尽管我不主张掌握差异的全部含义),display: none而是使用:

<button [style.display]="form.pristine ? 'inline' : 'none'">Yo</button>

6
我对ngIf与样式之间的区别的理解是,在条件为真之前ngIf不会在页面中包含HTML,从而在样式技术使HTML始终为在页面中,只是根据form.pristine的值隐藏或显示。
user3785010'7

4
您最好使用[hidden]而不是非常冗长的[style.display]部分。:)
Philipp Meissner '18

2
为什么不。尽管,如@Simon_Weaver在此页面上的另一条评论中所述,[hidden] 其行为将不会总是display: none
Arnaud P

1
我在每个按钮中显示了两个带有* ngIf的不同按钮(注销/登录),这引起了问题。
GoTo

施工人员对我来说是合适的地方,推出了一家小吃店
奥斯丁,

31

有一些有趣的答案,但我似乎找不到能满足我需求的答案,最接近的是@ chittrang-mishra,它仅指一个特定功能,而不像我的应用程序中涉及多个切换。

我不想利用甚至没有成为DOM一部分的[hidden]优势,*ngIf所以我发现以下解决方案可能并不是最好的解决方案,因为它抑制了错误而不是纠正了错误,但是对于我来说,我知道最终结果是正确的,对于我的应用来说似乎还可以。

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

ngAfterViewChecked(){
  this.changeDetector.detectChanges();
}

我希望这能对其他人有所帮助,对其他人有所帮助。


3
这不会触发无限变化检测循环吗?我的意思是,检查后您正在检测更改。
曼努埃尔·阿扎尔

@ManuelAzar显然不是。这是唯一为我工作的解决方案。最后我的控制台有些沉默。我对所有这些不相关的变更检测“错误”感到非常厌倦。
Jeremy Thille

30

Angular运行更改检测,当它发现传递到子组件的某些值已更改时,Angular会引发错误:

ExpressionChangedAfterItHasBeenCheckedError 点击更多

为了纠正这一点,我们可以使用AfterContentChecked生命周期挂钩和

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

  constructor(
  private cdref: ChangeDetectorRef) { }

  ngAfterContentChecked() {

    this.cdref.detectChanges();

  }

尽管这可能会解决问题,但这是否会使CD过于庞大和过度使用呢?
尼基

我认为这是解决此错误的唯一答案,该错误是由于将值传递给孩子而引起的。谢谢!
java-addict301

@Nicky是的。每次您在屏幕上的任何地方触摸ngAfterContentChecked()都会被调用
Mert Mertce

25

就我而言,在运行测试时,我的规格文件中存在此问题。

我不得不改变ngIf ,以 [hidden]

<app-loading *ngIf="isLoading"></app-loading>

<app-loading [hidden]="!isLoading"></app-loading>


2
此处的区别是,*ngIf更改DOM,在页面中添加和删除元素,而[hidden]在不更改DOM 的情况下更改项目的可见性。
Grungondola '18 -10-30

5
但是,这并不能真正解决真正的问题...?
ravo10

23

请按照以下步骤操作:

1.通过从@ angular / core导入'ChangeDetectorRef',如下所示:

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

2.在constructor()中实现它,如下所示:

constructor(   private cdRef : ChangeDetectorRef  ) {}

3.将以下方法添加到要在事件(如单击按钮)上调用的函数中。所以看起来像这样:

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

23

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

下面解决了我的问题:

我做了什么:

1. implement AfterViewChecked,  
2. add constructor(private changeDetector : ChangeDetectorRef ) {} and then 
3. ngAfterViewChecked(){ this.changeDetector.detectChanges(); }

它有帮助。惊人!!
Pathik Vejani

你救了我的日子...谢谢!
Ostan

19

我面临着同样的问题,因为值在组件的一个数组中更改。但是,我没有检测值更改的更改,而是将组件更改检测策略更改为onPush(它将检测对象更改而不是值更改的更改)。

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

@Component({
    changeDetection: ChangeDetectionStrategy.OnPush
    selector: -
    ......
})

这似乎可以动态添加/从控件中删除。.是否有任何缺点?
里卡多·萨拉奇诺

在我手头的情况下,就像魅力一样工作,谢谢!组件绑定到“全局”对象,该对象在其他位置已更改并导致错误发生。此组件已经具有用于更新绑定对象的更新处理程序,此事件处理程序现在将调用changeDetectorRef.detectChanges()与ChangeDetectionStrategy.OnPush结合使用,可以按预期运行,而不会出现错误。
Bernoulli IT

@RicardoSaracino您是否发现任何弊端?我想知道同样的事情。我知道变更检测OnPush的工作原理,但想知道是否可能我缺少一个陷阱。我不想回头。
mtpultz

@RicardoSaracino,是的,它有一些缺点,您可以参考此详细链接blog.angular-university.io/onpush-change-detection-how-it-works
Dheeraj

@BernoulliIT谢谢,我很高兴它为您服务。
Dheeraj

17

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

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

1)使用changeDetectorRef

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

 setTimeout(() => {
        this.isLoading = true;
    });

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

Promise.resolve(null).then(() => this.isLoading = true);

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


您可以将选项#3与样式表达一起使用吗?我有一个最后一个应该评估的高度样式表达式,因为它基于注入的内容。
N-ate

1
抱歉,您刚刚看到了您的评论,是的,我看不出有任何原因。因此,它也应该与样式更改一起使用。
ATHER

4

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

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

// image-carousel.component.ts
@HostBinding('style.background') 
style_groupBG: string;

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

@Input('carouselConfig')
public set carouselConfig(carouselConfig: string) 
{
    this.style_groupBG = carouselConfig.bgColor;   
}

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

@ViewChild(ImageCarousel) carousel: ImageCarousel;

ngAfterViewInit()
{
    this.carousel.carouselConfig = { bgColor: 'red' };
}

这是发生了什么:

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

一种解决方案是引入另一个wrapper div内部ImageCarousel并在其上设置背景色,但是这样您将无法获得使用的某些好处HostBinding(例如,允许父级控制对象的整个边界)。

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

ngAfterViewInit()
{
    this.carousel.carouselConfig = { ... };
    this.cdr.detectChanges();
}

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

考虑这种情况,@HostBinding直到开发后期才添加。突然您收到此错误,并且似乎没有任何意义。


2

这是我对正在发生的事情的想法。我尚未阅读文档,但可以确定这是显示错误的原因之一。

*ngIf="isProcessing()" 

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

[hidden]="isProcessing()"

使用时,[hidden]它实际上并没有改变,DOM只是element从视图中隐藏了,最有可能CSS在背面使用。元素仍在DOM中,但根据条件的值不可见。这就是使用时不会发生错误的原因[hidden]


如果isProcessing()是做AME的事情,你必须使用!isProcessing()[hidden]
马蒂厄CHARBONNIER

hidden不“在后面使用CSS”,而是常规的HTML属性。developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/...
拉扎尔Ljubenović

1

对于我的问题,我正在读的GitHub “中afterViewInit更改组件‘非模式’值时ExpressionChangedAfterItHasBeenCheckedError”,并决定添加ngModel -

<input type="hidden" ngModel #clientName />

它解决了我的问题,希望对您有所帮助。


1
它在该网站的哪个位置说要添加ngModel。您能否详细说明为什么这应该有所帮助?
彼得·威珀曼

当我跟踪此问题时,它导致我调查了链接。阅读文章后,我添加了属性,它解决了我的问题。如果有人遇到相同的问题,这将很有帮助。
Demodave

1

调试技巧

该错误可能会造成混乱,并且很容易对确切的发生时间做出错误的假设。我发现在适当位置的整个受影响的组件中添加许多这样的调试语句很有帮助。这有助于了解流程。

在这样的父级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');

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

    console.log('EXPRESSIONCHANGED - ChildComponent: setting config');
    console.log('EXPRESSIONCHANGED - ChildComponent: setting config ok');

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

    console.log('EXPRESSIONCHANGED - ChildComponent: running detectchanges');
    this.cdr.detectChanges();

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

在此处输入图片说明

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

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


1

就我而言,我有一个LoadingService带有BehavioralSubject 的async属性isLoading

使用[hidden]模型有效,但是* ngIf失败

    <h1 [hidden]="!(loaderService.isLoading | async)">
        THIS WORKS FINE
        (Loading Data)
    </h1>

    <h1 *ngIf="!(loaderService.isLoading | async)">
        THIS THROWS ERROR
        (Loading Data)
    </h1>

1

使用rxjs对我有用的解决方案

import { startWith, tap, delay } from 'rxjs/operators';

// Data field used to populate on the html
dataSource: any;

....

ngAfterViewInit() {
  this.yourAsyncData.
      .pipe(
          startWith(null),
          delay(0),
          tap((res) => this.dataSource = res)
      ).subscribe();
}

有问题的代码是什么?这里有什么解决方案?
mkb

@mkb,您好,问题是ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.在DOM更改时触发值更改时发生
Sandeep K Nair

嗨,我的意思是你在这里做了什么以克服这个问题。您之前根本没有使用过rxjs或没有添加delay()或没有添加startWith()吗?我已经将rxjs与各种rxjs方法一起使用,但仍然会收到错误,我希望解决这个问题:(
mkb

添加delay使错误消失。它的工作方式与相似setTimeout
拉扎尔(LazarLjubenović)

1

我在Ionic3(使用Angular 4作为其技术堆栈的一部分)中遇到了此类错误。

对我来说,这样做是:

<ion-icon [name]="getFavIconName()"></ion-icon>

因此,我试图根据屏幕运行的模式,有条件地将离子图标的类型从a pin更改为a remove-circle

我猜我将不得不添加一个*ngIf代替。


1

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

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

为了解决此问题,我将致电changeMyModelValue()地点更改为更有意义的地方。

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


0

我希望这对来这里的人有所帮助:我们以ngOnInit以下方式进行服务调用,并使用变量displayMain来控制将元素安装到DOM。

component.ts

  displayMain: boolean;
  ngOnInit() {
    this.displayMain = false;
    // Service Calls go here
    // Service Call 1
    // Service Call 2
    // ...
    this.displayMain = true;
  }

和component.html

<div *ngIf="displayMain"> <!-- This is the Root Element -->
 <!-- All the HTML Goes here -->
</div>

0

我收到此错误,是因为我在component.html中使用了一个未在component.ts中声明的变量。一旦删除了HTML中的零件,此错误就消失了。


0

我收到此错误,是因为我正在以模态分派redux动作,而当时没有打开模态。我在模态组件接收到输入时立即分派动作。因此,我将setTimeout放在此处以确保已打开模态,然后将动作插入。


0

对于任何为此苦苦挣扎的人。这是一种正确调试此错误的方法:https : //blog.angular-university.io/angular-debugging/

就我而言,确实我摆脱了使用此[隐藏] hack而不是* ngIf ...的错误。

但我提供的链接,使我能够找到有罪 * ngIf :)

请享用。


使用hidden而不是ngIf不是黑客,也根本不能解决问题的核心。您只是掩盖了问题。
LazarLjubenović

-2

解决方案...服务和rxjs ...事件发射器和属性绑定都使用rxjs ..您更好地实现了自己,更好的控制,更易于调试。请记住,事件发射器正在使用rxjs。简单地,创建一个服务并在可观察范围内,让每个组件订阅观察者并根据需要传递新值或假定值


1
这不仅不能回答问题,而且还是很糟糕的建议。伙计们,请不要仅仅因为遇到Angular的CD错误而自己重新实现rxjs。:)
LazarLjubenović
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.