我的组件具有依赖于当前日期时间的样式。在我的组件中,我有如下函数。

  private fontColor( dto : Dto ) : string {
    // date d'exécution du dto
    let dtoDate : Date = new Date( dto.LastExecution );

    (...)

    let color =  "hsl( " + hue + ", 80%, " + (maxLigness - lightnessAmp) + "%)";

    return color;
  }

lightnessAmp从当前datetime开始计算。如果dtoDate在过去24小时内,则颜色会发生变化。

准确的错误如下:

表达式在检查后发生了变化。先前值:'hsl(123, 80%, 49%)'。当前值:'hsl(123, 80%, 48%)'

我知道异常只出现在开发模式的值被检查的时刻。如果检查值与更新值不同,则抛出异常。

因此,我尝试在以下钩子方法中更新每个生命周期的当前datetime,以防止异常:

  ngAfterViewChecked()
  {
    console.log( "! changement de la date du composant !" );
    this.dateNow = new Date();
  }

...但是没有成功。


在更改之后显式地运行更改检测:

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

constructor(private cdRef:ChangeDetectorRef) {}

ngAfterViewChecked()
{
  console.log( "! changement de la date du composant !" );
  this.dateNow = new Date();
  this.cdRef.detectChanges();
}

博士TL;

ngAfterViewInit() {
    setTimeout(() => {
        this.dateNow = new Date();
    });
}

虽然这是一种变通方法,但有时真的很难以更好的方式解决这个问题,所以如果您使用这种方法,请不要责怪自己。没关系。

示例:初始问题[链接],用setTimeout()解决[链接]


如何避免

一般来说,这个错误通常发生在你添加ngAfterViewInit之后(甚至在父/子组件中)。第一个问题是问你自己,我能没有ngAfterViewInit吗?也许你把代码移到某个地方(ngAfterViewChecked可能是另一种选择)。

例如:[链接]


ngAfterViewInit中影响DOM的async也可能导致这个。也可以通过setTimeout或在管道中添加delay(0)操作符来解决:

ngAfterViewInit() {
  this.foo$
    .pipe(delay(0)) //"delay" here is an alternative to setTimeout()
    .subscribe();
}

例如:[链接]


好的阅读

关于如何调试以及为什么会发生这种情况的好文章:链接


正如@leocaseiro在github上提到的那样。

我为那些正在寻找简单解决方案的人找到了3个解决方案。 1)从ngAfterViewInit移动到ngAfterContentInit 2)移动到ngAfterViewChecked结合ChangeDetectorRef as 建议在#14748(评论) 3)继续使用ngOnInit(),但之后调用ChangeDetectorRef.detectChanges() 您的更改。


我认为你能想到的最好和最干净的解决方案是:

@Component( {
  selector: 'app-my-component',
  template: `<p>{{ myData?.anyfield }}</p>`,
  styles: [ '' ]
} )
export class MyComponent implements OnInit {
  private myData;

  constructor( private myService: MyService ) { }

  ngOnInit( ) {
    /* 
      async .. await 
      clears the ExpressionChangedAfterItHasBeenCheckedError exception.
    */
    this.myService.myObservable.subscribe(
      async (data) => { this.myData = await data }
    );
  }
}

用Angular 5.2.9测试过


一个小工作周围我用过很多次

Promise.resolve(data).then(() => { console.log (" !更改组件日期!”); this。dateNow = new Date(); this.cdRef.detectChanges (); });


给你两种解决方案!


1. 修改ChangeDetectionStrategy为OnPush

对于这个解决方案,你基本上告诉angular:

停止检查变更;我只会在必要的时候做

修改组件,使其使用ChangeDetectionStrategy。OnPush是这样的:

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent implements OnInit {
    // ...
}

有了这个,事情似乎就不管用了。这是因为从现在开始,你必须让Angular手动调用detectChanges()。

this.cdr.detectChanges();

如果你感兴趣,请查看这篇文章。它帮助我理解了ChangeDetectionStrategy是如何工作的。


2. 理解ExpressionChangedAfterItHasBeenCheckedError

请查看关于这个问题的视频(太棒了!)此外,这里是本文中关于此错误原因的一小部分摘录,我试图只包括有助于我理解此错误的部分。

整篇文章给出了关于这里所示的每一点的真实代码示例。

根本原因是angular生命周期的问题:

在每个操作之后,Angular都会记住它执行了什么值 一个操作。属性的oldValues属性中存储 组件视图。 检查完所有组件后,Angular就会启动 下一个摘要周期,但它不执行操作,而是将当前值与它记忆的值进行比较 前一个消化周期。

在摘要周期中检查以下操作:

检查传递给子组件的值是否与 用于更新这些组件属性的值 现在。

检查用于更新DOM元素的值是否与 用于更新这些元素的值现在执行 相同。

检查所有子组件

因此,当比较值不同时,就会抛出错误。,博主Max Koretskyi表示:

罪魁祸首总是子组件或指令。

最后,这里有一些现实世界中通常会导致这种错误的例子:

共享服务(示例) 同步事件广播(示例) 动态组件实例化(示例)

在我的例子中,问题是动态组件实例化。

此外,从我自己的经验来看,我强烈建议大家避免使用setTimeout解决方案,在我的情况下会导致“几乎”无限循环(21个调用,我不愿意向您展示如何激发它们),

我建议你时刻牢记Angular的生命周期,这样你就可以在每次修改另一个组件的值时考虑到它们会受到怎样的影响。对于这个错误,Angular会告诉你:

你可能做错了,你确定你是对的吗?

该博客还写道:

通常,解决方法是使用正确的更改检测钩子来创建动态组件


对我来说,一个简短的指南是在编码时至少考虑以下事情:

(我会尽量补充这一点):

Avoid modifying parent component values from its child's components, instead: modify them from their parent. When you use @Input and @Output directives try to avoid triggering lifecycle changes unless the component is completely initialized. Avoid unnecessary calls of this.cdr.detectChanges(); they can trigger more errors, especially when you're dealing with a lot of dynamic data When the use of this.cdr.detectChanges(); is mandatory make sure that the variables (@Input, @Output, etc) being used are filled/initialized at the right detection hook (OnInit, OnChanges, AfterView, etc) When possible, remove rather than hide, this is related to point 3 and 4. (same quote for angulardart) Avoid any kind of logic inside setters annotated with @Input, setters are executed previously to ngAfterViewInit so it'll easily trigger the issue. In case you need to, its better of to put that logic inside the ngOnChanges method.

Also

如果你想完全理解Angular Life Hook,我建议你阅读这里的官方文档: https://angular.io/guide/lifecycle-hooks


在我们的例子中,我们通过在组件中添加changeDetection来FIXED,并调用ngAfterContentChecked中的detectChanges(),代码如下所示

@Component({
  selector: 'app-spinner',
  templateUrl: './spinner.component.html',
  styleUrls: ['./spinner.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class SpinnerComponent implements OnInit, OnDestroy, AfterContentChecked {

  show = false;

  private subscription: Subscription;

  constructor(private spinnerService: SpinnerService, private changeDedectionRef: ChangeDetectorRef) { }

  ngOnInit() {
    this.subscription = this.spinnerService.spinnerState
      .subscribe((state: SpinnerState) => {
        this.show = state.show;
      });
  }

  ngAfterContentChecked(): void {
      this.changeDedectionRef.detectChanges();
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }

}

尽管已经有很多答案,并且有一篇关于变更检测的很好的文章的链接,但我还是想在这里发表我的意见。我认为检查是有原因的,所以我考虑了我的应用程序的架构,并意识到视图中的变化可以通过使用行为主体和正确的生命周期钩子来处理。这就是我的解。

I use a third-party component (fullcalendar), but I also use Angular Material, so although I made a new plugin for styling, getting the look and feel was a bit awkward because customization of the calendar header is not possible without forking the repo and rolling your own. So I ended up getting the underlying JavaScript class, and need to initialize my own calendar header for the component. That requires the ViewChild to be rendered befor my parent is rendered, which is not the way Angular works. This is why I wrapped the value I need for my template in a BehaviourSubject<View>(null): calendarView$ = new BehaviorSubject<View>(null);

接下来,当我可以确定视图被检查时,我用@ViewChild中的值更新该主题:

  ngAfterViewInit(): void {
    // ViewChild is available here, so get the JS API
    this.calendarApi = this.calendar.getApi();
  }

  ngAfterViewChecked(): void {
    // The view has been checked and I know that the View object from
    // fullcalendar is available, so emit it.
    this.calendarView$.next(this.calendarApi.view);
  }

然后,在我的模板中,我只使用异步管道。没有黑客与变更检测,没有错误,工作顺利。

如果你需要更多细节,请尽管问。


使用默认表单值可避免此错误。

而不是使用在ngAfterViewInit()中应用detectChanges()的接受答案(这也解决了我的情况下的错误),我决定为动态所需的表单字段保存一个默认值,这样当表单稍后更新时,如果用户决定更改表单上的选项,将触发新的必需字段(并导致提交按钮被禁用),它的有效性不会改变。

这在我的组件中节省了一小部分代码,在我的案例中,错误完全避免了。


我得到了这个错误,因为我声明了一个变量 使用ngAfterViewInit改变了它的值

export class SomeComponent {

    header: string;

}

来修复我从

ngAfterViewInit() { 

    // change variable value here...
}

to

ngAfterContentInit() {

    // change variable value here...
}

将代码从ngAfterViewInit移到ngAfterContentInit。

视图在内容之后被初始化,因此ngAfterViewInit()在ngAfterContentInit()之后被调用


以前所有的解决方案都不适合我,这是唯一一个真正管用的。

错误信息示例:

AppComponent.html:1错误 ExpressionChangedAfterItHasBeenCheckedError:表达式已经改变 在检查之后。之前的值:'ngIf: true'。当前值: “ngIf:假”。 在viewDebugError (core.js:20440) 在expressionChangedAfterItHasBeenCheckedError (core.js:20428) ...

解决方案 在触发问题的组件中导入ChangeDetectorRef, AfterContentChecked 然后加上:

 ngAfterContentChecked(): void {
    this.changeDetector.detectChanges();
  }

例子

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

import { Title } from '@angular/platform-browser';

import { environment } from '@env/environment';
import { LayoutService } from '@app/core/layout/layout.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: []
})
export class AppComponent implements OnInit, AfterContentChecked {
  env = environment;
  title = this.env.appName;
  partials: any = {};

  public constructor(
    private titleService: Title,
    public layout: LayoutService,
    private changeDetector: ChangeDetectorRef,
  ) {}

   ngOnInit() {
    this.titleService.setTitle(this.env.appName);
    
    this.layout.getLayout().subscribe(partials => {
      this.partials = partials;
    });
  }

  ngAfterContentChecked(): void {
    this.changeDetector.detectChanges();
  }
}

不是我的:这是来源 https://gist.github.com/vades/a225170304f34f88a7a5d48bf4b1f58c


你可以使用µsetTimeout()在javascript事件循环的下一个宏任务队列中运行属性的设置。 但是在这种情况下,使用detectChanges()就足够了。出现这种错误的原因有很多:

本文解释了产生此错误的所有原因以及此错误的所有解决方案:https://blog.simplified.courses/angular-expression-changed-after-it-has-been-checked-error/