在检查后,表达式 ___ 发生了变化。

444

为什么在这个简单的plunk中的组件会出现这种情况


@Component({
  selector: 'my-app',
  template: `<div>I'm {{message}} </div>`,
})
export class App {
  message:string = 'loading :(';

  ngAfterViewInit() {
    this.updateMessage();
  }

  updateMessage(){
    this.message = 'all done loading :)'
  }
}

抛出异常:

异常:表达式“我在 App@0:5 中是{{message}}”在被检查后已更改。先前的值为:“我正在加载:( ”。当前值为:“我完成了所有加载:) ”,位于 [我在 App@0:5 中是{{message}}]

当我只是在视图初始化时更新一个简单绑定时发生了什么?


10
这篇文章 关于 ExpressionChangedAfterItHasBeenCheckedError 错误,你需要了解的一切 详细地解释了其行为。 - Max Koretskyi
考虑在使用detectChanges()时修改您的ChangeDetectionStrategy。https://dev59.com/JlkS5IYBdhLWcg3we22Z#54954374 - luiscla27
1
仅考虑一个输入控件并在一个方法中填充数据的情况,同时在同一方法中为其分配某个值。编译器肯定会对新/旧值感到困惑。因此,绑定和填充应该在不同的方法中进行。 - Ram
正如@Jo-VdB所问,难道你不能使用ngOnInit()来更新绑定吗? - oomer
23个回答

6
简单来说,首先要在组件构建时分离/删除变更检测,然后在ngAfterViewInit()方法中启用detectChanges()
constructor(private cdr: ChangeDetectorRef) {
  this.cdr.detach() // detach/remove the change detection here in constructor
}


ngAfterViewInit(): void {
  // do load objects or other logics here
  
  // at the end of this method, call detectChanges() method.
  this.cdr.detectChanges(); // enable detectChanges here and you're done.
}

5

您也可以在ngOnInt()方法中调用updateMessage(),这对我来说至少是有效的。

ngOnInit() {
    this.updateMessage();
}

在RC1中,这不会触发异常。

4
您也可以使用rxjs的Observable.timer函数创建一个计时器,然后在订阅中更新消息:
Observable.timer(1).subscribe(()=> this.updateMessage());

3

因为在调用ngAfterViewInit()时,您的代码被更新了,所以它会抛出错误。这意味着当ngAfterViewInit发生时,您的初始值已经被更改。如果您在ngAfterContentInit()中调用它,则不会抛出错误。

ngAfterContentInit() {
    this.updateMessage();
}

2
你也可以尝试在ngOnInit下面添加this.updateMessage();,像这样:
ngOnInit(): void { 
  this.updateMessage();
}

是的,在我的情况下,ngAfterViewInit() 是导致问题的原因,所以我只是将其放在了 ngOnInit 中。 - taggartJ

2
我有一个几乎相同的情况,我有一个产品数组。我必须让用户根据他们的选择删除产品。最后,如果数组中没有任何产品,则需要显示取消按钮而不是返回按钮,而无需重新加载页面。
我通过在ngAfterViewChecked()生命周期钩子中检查空数组来完成它。 这就是我完成的方式,希望它有所帮助 :)
import { ChangeDetectorRef } from '@angular/core';

products: Product[];
someCondition: boolean;

constructor(private cdr: ChangeDetectorRef) {}

ngAfterViewChecked() {
  if(!this.someCondition) {
    this.emptyArray();
  }
}

emptyArray() {
    this.someCondition = this.products.length === 0 ? true : false;

    // run change detection explicitly
    this.cdr.detectChanges();
}

removeProduct(productId: number) {
    // your logic for removing product.
}

1

1
在我的情况下,出现了一个p-radioButton的问题。问题是我使用了name属性(其实不需要)和formControlName属性,就像这样:
<p-radioButton formControlName="isApplicant" name="isapplicant" value="T" label="Yes"></p-radioButton>
<p-radioButton formControlName="isApplicant" name="isapplicant" value="T" label="No"></p-radioButton>

我还将初始值“T”绑定到isApplicant表单控件,方法如下:
isApplicant: ["T"]

我通过移除单选按钮中的名称属性来解决了问题。此外,由于两个单选按钮具有相同的值(T),这在我的情况下是错误的,只需将其中一个更改为另一个值(例如F)也可以解决该问题。

0

好答案。但是,在我使用 ChangeDetectorRef 和 AfterViewInit 时,似乎 Angular 会进入几个额外的渲染周期,如果我的 HTML 代码没有经过非常好的设计或需要多次调用依赖于刷新的 TS 代码函数,我会收到额外的视图渲染调用,因此会有额外的处理。

这里是一个我喜欢使用的解决方案,因为我不必担心任何这些问题,它在程序上非常简单,而且对我或系统的要求也不需要太多。无论何时 Angular 给我带来困扰,出现臭名昭著的错误:“检查后表达式已更改”,我都可以毫不费力地使用它。

我有这个小公共/导出函数,它只是通过一个零延迟 Promise 传递我的值。这样做的效果是,它强制 JavaScript/JS 进入另一个后台循环,从而将值更新分离到下一个处理周期,并防止出现错误。(请注意,JS 循环与 Angular HTML 视图渲染循环不同,并且处理强度较小)。

export async function delayValue(v: any, timeOutMs: number = 0): Promise<any> {
    return new Promise((resolve) => {
        setTimeout(() => {
          resolve(v);
        }, timeOutMs);
      });
}

现在,当我需要防止错误时,我只需执行以下操作:
this.myClassValue = await delayValue(newValue);

这只是一行代码。由于timeOutMs的值为0,因此实际上没有明显的延迟。

以下是一个典型的场景:

myObservable$.subscribe(newValue  = {
    ...                // WHEN NEW VALUE ARRIVES FROM NOTIFIER(S)
    this.handleSubscribedValues(newValue);
    ...
});
                      // THIS MAY GIVE YOU THE ERROR !
private handleSubscribedValues(newValue) {
    this.myClassValue = newValue;
}
                      // SO, USE THIS INSTEAD TO AVOID THE ERROR
private async handleSubscribedValues(newValue) {
    this.myClassValue = await delayValue(newValue);
}

如果您需要等待某些事件发生,例如让用户有几秒钟的时间,您还可以使用delayValue()函数和一些延迟/超时值。

希望这对您中的一些人有所帮助。


0
在处理datatable时,我遇到了类似的错误。当您在另一个*ngFor中使用*ngFor时,datatable会抛出此错误,因为它会干扰angular更改周期。因此,不要在datatable内部使用datatable,而是使用一个普通的表格或者用数组名称替换mf.data,这样就可以正常工作了。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接