ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked - How to update template after Observable Value change

ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked - How to update template after Observable Value change

我浏览了很多 SO 帖子试图找到解决这个问题的方法,我发现的唯一一个有 hack 实现。我有一个从我订阅的 ngrx 商店获取的观察值:

this.navigationSelected$ = this.store.pipe(select(currentlySelectedNavigation));

this.navigationSelected$.subscribe(res => {
  ...
});

根据模板内的这个可观察值使用 ngIf:

<profile-navigation *ngIf="(navigationSelected$ | async) == navigationLayout[0].location"></profile-navigation>

只要 navigationSelected$ 的值发生变化,就会抛出:

ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'ngIf: [object Object]'. Current value: 'ngIf: false'.

而且模板没有更新。我设法绕过了 运行 cdRef.detectChanges();在订阅结束时。它工作正常,但仍然会抛出错误,而且如前所述,这似乎是一个 hack。

实现我想要做的事情的最佳方式是什么?

Whenever the value of navigationSelected$ changes, this throws:

错误的意思是值改变了两次。

当您在选择器上出现这些错误时,可能很难修复它们。这个观点真的没有任何问题。问题是商店在呈现视图之前和之后改变状态,这可能意味着在调用 setTimeout() 之后应该发生调度。

问题在于,这会使您的源代码中的某些其他位置依赖于更改状态以防止视图触发错误。这并不理想。

另一种方法是使用 EventEmitter.

发出值
<profile-navigation *ngIf="(navigationSelectedSafe$ | async) == navigationLayout[0].location"></profile-navigation>

public navigationSelectedSafe$ = new EventEmitter<any>(true); // must be true

this.navigationSelected$.subscribe(res => navigationSelectedSafe$.emit(res));

当您使用 EventEmitter(true) 时,它将在 setTimeout() 之后发出值,从而保护视图免受更改错误的影响。

您还可以搜索您的源代码中使用@Output()的地方,看看将其更改为EventEmitter(true)是否可以解决问题。

通常当您在选择器上看到此错误时。这意味着你在视图之外做了很多与状态相关的工作。需要广播某些内容已更改的组件应该使用 @Output(),但如果该组件正在调度,则它会绕过视图进程。这是您 运行 遇到这些问题的地方。

我通常会添加一个 debounceTime(0) 来解决这个问题。

this.navigationSelected$ = this.store.pipe(select(currentlySelectedNavigation), debounceTime());