Facing "ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked" to show/hide loader

Facing "ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked" to show/hide loader

我在父组件 (AppComponent) 中有一个通用加载器和 router-outlet(用于加载子组件)。在子组件中,我们更改值以分别在调用 API 之前和之后显示和隐藏加载程序。因此发生此错误。

例如这是我在 app.cpmponent.html:

中的加载程序
<p [ngClass]="(overlappingProgress | async) ? 'display-block' : 'display-none'">holaaaaaaaaaaaaaaa</p>

这里overlappingProgress是一个observable类型的变量,通过async管道解析。

在子组件中,我有一个需要来自服务器的一些数据的表单。因此,在子组件的 ngOnInit 中,我按以下顺序发送了一些操作(因为我们正在使用 ngrx)-

  1. showOverlappingProgress() // 它使 reducer
  2. 中的加载 属性 为真
  3. 获取数据()
  4. hideOverlappingProgress() // 它使得在 reducer
  5. 中加载 属性 false

因此,在 app.component.ts 中检测该更改,我监听更改以更新 show/hide 加载程序。

this.overlappingProgress = this.store.select(s => s.app.overlappingProgress);

我有一些想法,它将 previous/old 值与第一个摘要周期的值进行比较,但是该值在摘要周期内发生变化,因此,当它运行第二个摘要周期时,值不匹配,因此发生此错误。

我在 stackblitz.

中使用更简单的版本来模拟这种情况

我找到了一些建议使用 setTimeout()this.cdr.detectChanges() 的解决方案。

个人感觉setTimeout()不太行。此外,手动触发 detectchanges 可能会做很多额外的工作,因为所有的子组件都在使用这个加载器,并且每次它都会触发和额外的变化检测。

所以,我想就这种情况下最好的解决方案提出一些建议。

有父子关系。子组件更改父组件表达式中使用的父组件的值。

To solve the issue, I changed the parent-child structure a bit. I have created a new module called FfLoadingIndicatorComponentModule and it has a component called FfLoadingIndicatorComponent. Inside that component I subscribed the changes it show hide the loader respectively. Finally I import that module in AppModule to use the newly created loading indicator component from app component.

这里最主要的是独立组件及其与应用组件的关系。让我们看看代码。 代码示例:

ff-加载-indicator.component.ts

@Component({
    selector: 'ff-loading-indicator',
    templateUrl: './ff-loading-indicator.component.html',
    styleUrls: ['./ff-loading-indicator.component.scss']
})
export class FfLoadingIndicatorComponent implements OnInit {
    progress$: Observable<boolean>;
    overlappingProgress$: Observable<boolean>;

    constructor(private store: Store<State>) { }
    ngOnInit(): void {
        this.progress$ = this.store.select(s => s.app.progress);
        this.overlappingProgress$ = this.store.select(s => s.app.overlappingProgress);
    }
}

ff-加载-indicator.component.html

<div [ngClass]="(progress$ | async) ? 'display-block' : 'display-none'">
    <mat-progress-bar class="ff-z-index-above-toolbar ff-general-loading-indicator" mode="indeterminate" color="accent"></mat-progress-bar>
</div>

并最终在 app.component.html 中使用此组件。

app.component.html

<ff-loading-indicator></ff-loading-indicator>

您可以在 stackblitz 中找到更改。

Note: FfLoadingIndicatorComponent can be imported directly in AppModule. As I preferred the module approach, I created a separate module and import that in AppModule