请向我解释为什么我一直得到这个错误:ExpressionChangedAfterItHasBeenCheckedError:表达式已经改变后,它被检查。

显然,我只有在开发模式下才会遇到这种情况,在我的产品构建中不会出现这种情况,但这非常烦人,而且我根本不明白在我的开发环境中出现错误而不会在prod上显示的好处——可能是因为我缺乏理解。

通常,修复很简单,我只是把导致错误的代码包装在setTimeout中,就像这样:

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

或者使用如下构造函数强制检测更改:

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

但是为什么我总是遇到这个错误呢?我想要了解它,这样我就可以在将来避免这些俗套的修复。


当前回答

我希望这能帮助到在座的各位: 我们在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>

其他回答

更新

我强烈建议首先从OP的自我响应开始:适当地考虑在构造函数中可以做什么,而在ngOnChanges()中应该做什么。

原始

这与其说是一个答案,不如说是一个题外话,但它可能会帮助到一些人。当我试图让按钮的存在取决于表单的状态时,我偶然发现了这个问题:

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

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

在我的案例中,解决方案(尽管我没有声称掌握了差异的全部含义)是使用display: none来代替:

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

在@ViewChild装饰器中添加static: true

@ViewChild(UploadComponent, { static: true }) uploadViewChild: UploadComponent;

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

*ngIf="isProcessing()" 

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

[hidden]="isProcessing()"

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

我的问题是我在加载这个对象时打开了一个Ngbmodal弹出框,这个对象在被检查后被改变了。我能够通过打开setTimeout内的模态弹出框来解决它。

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

尝试了上面建议的大部分解决方案。只有在这种情况下对我有用。我使用*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();
        });
    });
}

诀窍是使用ngzone绕过角分量的变化检测。

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