ExpressionChangedAfterItHasBeenCheckedError 解释

Posted

技术标签:

【中文标题】ExpressionChangedAfterItHasBeenCheckedError 解释【英文标题】:ExpressionChangedAfterItHasBeenCheckedError Explained 【发布时间】:2017-09-08 14:07:01 【问题描述】:

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

显然,我只在开发模式下获得它,它不会发生在我的生产版本中,但这很烦人,我根本不明白在我的开发环境中出现错误不会出现的好处on prod——可能是因为我缺乏了解。

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

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

或者使用这样的构造函数强制检测更改:constructor(private cd: ChangeDetectorRef) :

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

但是为什么我经常遇到这个错误?我想了解它,以便将来避免这些骇人听闻的修复。

【问题讨论】:

Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedError error 详细解释了这种行为 我也面临同样的问题,它只发生在 DEV 上并且只影响我的控制台日志。我没有在项目中引入任何新代码,而是简单地从控制台隐藏/阻止此错误。 Angular 官方指南 - angular.io/errors/NG0100 你提到的解决方案不是一个hacky,而是一个适当的解决方案。此处描述:blog.angular-university.io/angular-debugging 【参考方案1】:

此错误表明您的应用程序中确实存在问题,因此引发异常是有意义的。

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

如果模型在常规和附加变化检测轮次之间发生了变化,这表明要么

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

这两个都不好,因为模型可能永远不会稳定,所以不清楚如何进行。

如果 Angular 在模型稳定之前运行变化检测,它可能会一直运行。 如果 Angular 不运行变更检测,那么视图可能不会反映模型的当前状态。

另见What is difference between production and development mode in Angular2?

【讨论】:

我怎样才能避免将来看到这个错误?我需要以不同的方式来思考我的代码,以确保我不会犯同样的错误吗? 这通常是由一些生命周期回调引起的,例如ngOnInitngOnChanges 修改模型(一些生命周期回调允许修改模型其他人不允许,我不记得自己到底是哪一个做或不做)。不要绑定到视图中的方法或函数,而是绑定到字段并更新事件处理程序中的字段。如果您必须绑定到方法,请确保它们始终返回相同的值实例,只要实际上没有更改。变更检测会大量调用这些方法。 不一定是应用程序的问题。调用changeRef.detectChanges() 是一种解决方案/抑制错误的事实证明了这一点。这就像在 Angular 1 中修改 $scope.$watch() 内部的状态。 我不太了解 Angular 1,但 Angular 2 中的变更检测工作方式完全不同。你是对的,这不一定是一个问题,但通常cdRef.detectChanges() 只在一些奇怪的边缘情况下是必需的,当你需要它时,你应该仔细查看,以正确理解原因。 如果我的 html 通过get ClockValue() return DateTime.TimeAMPM(new Date()) 绑定到一个吸气剂返回时间为“HH:MM”,它最终会在检测运行时发生变化时跳闸,我该如何解决这个问题?跨度> 【参考方案2】:

更新

我强烈建议首先从the OP's self response 开始:正确考虑在constructor 中可以做什么与在ngOnChanges() 中应该做什么。

原创

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

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

据我所知,这种语法会导致按钮根据条件从 DOM 中添加和删除。这又导致ExpressionChangedAfterItHasBeenCheckedError

在我的情况下,解决方法是使用 display: none 代替(尽管我没有声称掌握差异的全部含义):

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

【讨论】:

我对 ngIf 与样式之间的区别的理解是,ngIf 在条件为真之前不会将 HTML 包含在页面中,因此会稍微减少“页面权重”,而样式技术使 HTML 始终在页面中,并且根据 form.pristine 的值简单地隐藏或显示。 您不妨使用[hidden] 而不是非常冗长的[style.display] 部分。 :) 为什么不呢。虽然,正如@Simon_Weaver 在此页面的另一条评论中所提到的,[hidden] will not always have the same behavior 为 display: none 我显示了两个不同的按钮(注销/登录),每个按钮中都带有 *ngIf,这是导致问题的原因。 constructor 是适合我的地方,推出了一个材料小吃店【参考方案3】:

我遇到了类似的问题。查看lifecycle hooks documentation,我将ngAfterViewInit 更改为ngAfterContentInit,并且成功了。

【讨论】:

@PhilipEnc 我的问题与 DOM 更改触发的更改有关。当 DOM 发生变化时,QueryList 对象(来自 @ContentChildren 属性)将更新,并且在更新调用它的方法内部更改了双向绑定属性。这造成了我遇到的问题。使用setTimeout 将更改包装到两个属性,就像您在上面显示的那样成功了。谢谢! 在我的例子中,我在 ngAfterContentInit 中放置了一些更改primeng 网格数组值的代码,我将代码放在 ngOnInit 中并且它起作用了。 ngAfterContentChecked 在这里工作,而ngAfterContentInit 仍然抛出错误。 ngAfterContentChecked 使用但项目加载速度很慢【参考方案4】:

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

对我来说是这样的:

&lt;ion-icon [name]="getFavIconName()"&gt;&lt;/ion-icon&gt;

所以我试图根据屏幕运行的模式有条件地将ion-icon 的类型从pin 更改为remove-circle

我猜我必须添加一个*ngIf

【讨论】:

【参考方案5】:

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

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

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

【讨论】:

这似乎适用于动态添加/删除控件..有什么缺点吗? 在我手头的情况下就像一个魅力,谢谢!一个组件绑定到一个“全局”对象,该对象在其他地方被更改并导致错误发生。此组件已经有一个更新处理程序,用于更新绑定对象时,此事件处理程序现在调用 changeDetectorRef.detectChanges() 结合 ChangeDetectionStrategy.OnPush 这可以按预期工作而不会出现错误。 @RicardoSaracino 你有没有发现任何缺点?我想知道同样的事情。我知道更改检测 OnPush 是如何工作的,但想知道是否存在我可能遗漏的问题。我不想绕回去。 @RicardoSaracino ,是的,它有一些缺点,你可以参考这个详细的链接blog.angular-university.io/onpush-change-detection-how-it-works @BernoulliIT 谢谢,我很高兴它对你有用。【参考方案6】:

一旦我理解了the Angular Lifecycle Hooks 以及它们与变更检测的关系,就会产生很多理解。

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

根据文档,这个方法是在 Angular 已经检测到变化之后调用的:

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

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

就我而言,我改变了这个:

constructor(private globalEventsService: GlobalEventsService) 



ngOnInit() 
    this.globalEventsService.showCheckoutHeader = true;

到这里:

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


ngOnInit() 


它解决了问题:)

【讨论】:

我的问题类似。长时间后我犯了一个错误,并在 ngOnInit 函数和构造函数之外定义了一个变量。这接收来自可观察对象的数据更改,该可观察对象放置在初始化函数中。做了和你一样的事情来修复错误。 非常相似,但我试图在加载到 URL 中存在的片段时滚动 (router.navigate)。这段代码最初放在AfterViewInit 中,我收到了错误,然后我按照你对构造函数的说法移动了,但它不尊重片段。移至 ngOnInit 已解决 :) 谢谢! 如果我的 html 通过 get ClockValue() return DateTime.TimeAMPM(new Date()) 绑定到 getter 返回时间为“HH:MM”怎么办?它最终会在分钟变化时跳闸在检测运行时,我该如何解决这个问题? 这里也一样。还发现包装到 setInterval() 也可以在其他生命周期事件代码之后触发。 得到了一个非常相似的场景,虽然我改变的状态不是布尔标志而是一些枚举(状态)字段。修复方法是用“ngIf”包装引用这个有问题的状态字段的html,这将检查其他一些已经初始化的字段。【参考方案7】:

就我而言,我在运行测试时在我的规范文件中遇到了这个问题。

我不得不将ngIf 改为 [hidden]

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

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

【讨论】:

关于[hidden]:talkingdotnet.com/dont-use-hidden-attribute-angularjs-2 这里的区别在于*ngIf 改变了 DOM,在页面中添加和移除元素,而[hidden] 改变了项目的可见性,而不是从 DOM 中移除。 但是,这并没有真正解决真正的问题...? 当这些隐藏字段中的一个发生更改时,这触发了我所有的验证器,而不是一个漂亮的网站,可以看到所有内容都是红色的!【参考方案8】:

对于我的问题,我正在阅读 github - “ExpressionChangedAfterItHasBeenCheckedError when changed a component 'non model' value in afterViewInit”并决定添加 ngModel

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

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

【讨论】:

该网站上的哪个位置说要添加ngModel。您能否详细说明为什么这会有所帮助? 当我追踪这个问题时,它引导我调查链接。阅读文章后,我添加了属性并解决了我的问题。如果有人遇到同样的问题会很有帮助。【参考方案9】:

按照以下步骤操作:

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();     

【讨论】:

【参考方案10】:

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

*ngIf="isProcessing()" 

当使用 *ngIf 时,它会在每次条件改变时通过添加或移除元素来物理地改变 DOM。因此,如果条件在呈现到视图之前发生变化(这在 Angular 的世界中很有可能),就会引发错误。开发模式和生产模式之间的解释见here。

[hidden]="isProcessing()"

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

【讨论】:

如果isProcessing() 做同样的事情,你必须使用!isProcessing() 作为[hidden] hidden 没有“在后面使用 CSS”,它是一个常规的 HTML 属性。 developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/…【参考方案11】:

参考文章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;
    );

3) 如果你真的想在同一个虚拟机上执行你的代码,可以使用

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

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

【讨论】:

您可以将选项#3 与样式表达式一起使用吗?我有一个应该最后评估的高度样式表达式,因为它基于注入的内容。 抱歉刚刚看到您的评论,是的,我看不出有什么理由不这样做。所以这也应该适用于样式更改。【参考方案12】:

@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) 在ngAfterViewInit() 之前我们无法访问carousel(它将为空) 我们分配配置,设置style_groupBG = 'red' 这反过来又在宿主 ImageCarousel 组件上设置 background: red 此组件由您的父组件“拥有”,因此当它检查更改时,它会在 carousel.style.background 上发现更改,并且不够聪明,无法知道这不是问题,因此会引发异常。

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

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

ngAfterViewInit()

    this.carousel.carouselConfig =  ... ;
    this.cdr.detectChanges();

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

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

【讨论】:

【参考方案13】:

调试提示

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

在这样的父 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),还需要注意另一件事,请确保您正在调试您认为的属性 - 将其设置为模糊的颜色值。

【讨论】:

【参考方案14】:

当我添加 *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

【讨论】:

【参考方案15】:

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

我不想使用 [hidden] 来利用 *ngIf 甚至不是 DOM 的一部分,所以我发现以下解决方案可能不是对所有人最好的,因为它抑制了错误而不是纠正它,但在我知道最终结果正确的情况下,我的应用程序似乎没问题。

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

ngAfterViewChecked()
  this.changeDetector.detectChanges();

我希望这对其他人有所帮助,就像其他许多人帮助过我一样。

【讨论】:

这不会触发无限变化检测循环吗?我的意思是,您在检查后正在检测更改。 @ManuelAzar 显然不是。这是唯一对我有用的解决方案。我的控制台终于安静了。我厌倦了所有这些无关的变更检测“错误”。 抑制错误是什么意思?不是changeDetector检测到变化吗? @ManuelAzar 没有无限循环,因为ngAfterViewChecked 在组件生命周期中只被调用一次。触发变更检测周期不会重新启动组件生命周期,因此不会再次调用ngAfterViewChecked,除非从头开始重新渲染组件。 (请注意,其他一些挂钩,例如 ngDoCheck,将由额外的变更检测周期触发,因为它们会误导性地挂钩到变更检测周期,而不是直接连接到组件生命周期。在 ngDoCheck 中使用 detectChanges() 会导致一个无限循环。)【参考方案16】:

Angular 运行更改检测,当它发现某些已传递给子组件的值已更改时,Angular 会抛出以下错误:

ExpressionChangedAfterItHasBeenCheckedErrorclick for more

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

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

  constructor(
  private cdref: ChangeDetectorRef)  

  ngAfterContentChecked() 

    this.cdref.detectChanges();

  

【讨论】:

虽然这可能会解决问题,但这不是非常膨胀和过度杀伤CD吗? 我认为这是解决由于将值传递给孩子而导致的此错误的唯一答案。谢谢! @Nicky 是的。每次触摸屏幕时,任何地方都会调用 ngAfterContentChecked() 此解决方案帮助我解决了将更改从子组件传递回 app.components.ts 的问题。谢谢 我们是在子组件还是父组件中执行此操作?只是遇到同样的问题,并试图更好地理解根本问题。【参考方案17】:

就我而言,我在LoadingService 中有一个异步属性,其行为主题为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>

【讨论】:

【参考方案18】:

解决方案...服务和 rxjs...事件发射器和属性绑定都使用 rxjs..您最好自己实现它,更多控制,更容易调试。请记住,事件发射器正在使用 rxjs。简单地说,创建一个服务并在一个 observable 中,让每个组件订阅这个观察者,并根据需要传递新值或 cosume 值

【讨论】:

这不仅不能回答问题,而且还是很糟糕的建议。伙计们,请不要仅仅因为遇到 Angular 的 CD 错误就自己重新实现 rxjs。 :)【参考方案19】:

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

组件.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>

【讨论】:

【参考方案20】:

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

【讨论】:

【参考方案21】:

我收到此错误是因为我在模态中调度 redux 操作,而当时未打开模态。在模态组件收到输入的那一刻,我正在调度动作。所以我把 setTimeout 放在那里,以确保打开模式,然后分配动作。

【讨论】:

【参考方案22】:

使用 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,问题是ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked.当DOM更改时触发值更改时发生 嗨,我的意思是你在这里做了什么来克服这个问题。你之前根本没用过rxjs,还是加了delay(),还是加了startWith()?我已经将 rxjs 与各种 rxjs 方法一起使用,但仍然出现错误,我希望能解决这个问题:( 添加的delay 使错误消失。它的工作原理类似于setTimeout【参考方案23】:

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

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

    实施AfterViewChecked 添加constructor(private changeDetector : ChangeDetectorRef ) 然后ngAfterViewChecked() this.changeDetector.detectChanges();

【讨论】:

【参考方案24】:

致所有为此苦苦挣扎的人。这是正确调试此错误的方法:https://blog.angular-university.io/angular-debugging/

就我而言,确实我使用这个 [hidden] hack 而不是 *ngIf...

摆脱了这个错误

但是我提供的链接使我能够找到 THE GUILTY *ngIf :)

享受吧。

【讨论】:

使用hidden 代替ngIf 不是黑客,也根本没有解决问题的核心。你只是在掩盖问题。【参考方案25】:

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

在有问题的组件中:

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

ngOnInit() 
    this.ngZone.runOutsideAngular(() => 
        this.appService.appLoader$.subscribe(value => 
            this.loading = value;
            this.changeDetectorRef.detectChanges();
        );
    );

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

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

【讨论】:

【参考方案26】:

我的问题是我在加载这个检查后正在更改的对象时打开了一个 Ngbmodal 弹出窗口。我可以通过在 setTimeout 中打开模式弹出窗口来解决它。

setTimeout(() => 
  this.modalReference = this.modalService.open(this.modal,  size: "lg" );
);

【讨论】:

【参考方案27】:

我在 RxJS/Observables 和静态模拟数据之间遇到了这个问题。起初,我的应用程序使用静态模拟数据,在我的例子中是数据数组

html 是这样的:

*ngFor="let x of myArray?.splice(0, 10)"

所以这个想法是只显示来自myArray 的最多 10 个元素。 splice() 获取原始数组的副本。据我所知这在 Angular 中非常好

然后我将数据流更改为 Observable 模式,因为我的“真实”数据来自 Akita(一个状态管理库)。这意味着我的 html 变成了:

*ngFor="let x of (myArray$ | async)?.splice(0, 10)"

其中 myArray$ 是 [was] 类型的 Observable&lt;MyData[]&gt;,模板中的这种数据操作是导致错误发生的原因。不要对 RxJS 对象这样做。

【讨论】:

【参考方案28】:

当一个值在同一个更改检测周期中更改多次时会发生此错误。我遇到了一个 TypeScript getter 的问题,它的返回值变化非常频繁。要解决此问题,您可以限制一个值,使其在每个更改检测周期中只能更改一次,如下所示:

import  v4 as uuid  from 'uuid'

private changeDetectionUuid: string
private prevChangeDetectionUuid: string
private value: Date

get frequentlyChangingValue(): any 
  if (this.changeDetectionUuid !== this.prevChangeDetectionUuid) 
    this.prevChangeDetectionUuid = this.changeDetectionUuid
    this.value = new Date()
  
  return this.value


ngAfterContentChecked() 
  this.changeDetectionUuid = uuid()

HTML:

<div> frequentlyChangingValue </div>

这里的基本做法是每个变更检测周期都有自己的uuid。当 uuid 发生变化时,您就知道您处于下一个循环中。如果循环已更改,则更新值并返回它,否则只返回与此循环先前返回的相同的值。

这样可以确保每个循环只返回一个值。鉴于更改检测周期如此频繁地发生,这对于频繁更新值非常有效。

要生成 uuid,我使用了 uuid npm 模块,但您可以使用任何生成唯一随机 uuid 的方法。

【讨论】:

【参考方案29】:

setTimeout 或 delay(0) 如何解决这个问题?

这就是上面的代码解决问题的原因:

The initial value of the flag is false, and so the loading indicator will NOT be displayed initially

ngAfterViewInit() gets called, but the data source is not immediately called, so no modifications of the loading indicator will be made synchronously via ngAfterViewInit()

Angular then finishes rendering the view and reflects the latest data changes on the screen, and the javascript VM turn completes

One moment later, the setTimeout() call (also used inside delay(0)) is triggered, and only then the data source loads its data

the loading flag is set to true, and the loading indicator will now be displayed

Angular finishes rendering the view, and reflects the latest changes on the screen, which causes the loading indicator to get displayed

这次没有出现错误,因此修复了错误消息。

来源:https://blog.angular-university.io/angular-debugging/

【讨论】:

以上是关于ExpressionChangedAfterItHasBeenCheckedError 解释的主要内容,如果未能解决你的问题,请参考以下文章