You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多次订阅同一Observable触发ExpressionChangedAfterItHasBeenCheckedError求助

问题描述

在组件视图中通过async pipe两次订阅同一个Observable:

<ng-container *ngIf="isLoading$ | async">
    Is Loading
</ng-container>

<ng-container *ngIf="!(isLoading$ | async)">
    Is Not Loading
</ng-container>

组件.ts相关代码:

isLoading$: Observable<boolean>;

ngOnInit() {
    this.isLoading$ = this.loadingService.loading$;
}

Loading Service代码:

export class LoadingService {
  
  private loadingSource: ReplaySubject<boolean> = new ReplaySubject<boolean>(1);
  private loadingCount = 0;
  public loading$: Observable<boolean> = this.loadingSource.asObservable();

  private isLoading(): boolean {
    return this.loadingCount > 0;
  }

  public startLoad() {
    this.loadingCount++;
    this.loadingSource.next(this.isLoading());  
  }

  public endLoad() {
    this.loadingCount--;
    this.loadingSource.next(this.isLoading());
  }
}

Loading Interceptor代码:

export class LoadingInterceptor implements HttpInterceptor {

  constructor(private loadingService: LoadingService) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    this.loadingService.startLoad();
    return next.handle(req)
      .pipe(
        share(),
        finalize(() => {
          this.loadingService.endLoad();
        })
      );
  }
}

运行后触发NG0100: ExpressionChangedAfterItHasBeenCheckedError错误,提示:

Expression has changed after it was checked. Previous value: 'true'. Current value: 'false'.

问题原因

两次async pipe会创建两个独立的订阅:当拦截器中请求完成触发finalize调用endLoad时,Observable的值从true变为false。Angular变更检测周期中,第一次订阅已检查值为true,第二次订阅时值已更新为false,导致前后状态不一致,触发错误。同时ReplaySubject的重播特性会加剧这种多订阅的状态差异。

解决方案

方案1:单次订阅复用结果

在模板中仅订阅一次,通过as语法将结果存入变量后复用,从根源避免多订阅问题:

<ng-container *ngIf="isLoading$ | async as isLoading">
    <ng-container *ngIf="isLoading">Is Loading</ng-container>
    <ng-container *ngIf="!isLoading">Is Not Loading</ng-container>
</ng-container>

方案2:共享Observable订阅

在Loading Service中为loading$添加shareReplay(1)操作符,让所有订阅共享同一数据源,确保状态一致:

import { shareReplay } from 'rxjs/operators';

export class LoadingService {
  
  private loadingSource: ReplaySubject<boolean> = new ReplaySubject<boolean>(1);
  private loadingCount = 0;
  public loading$: Observable<boolean> = this.loadingSource.asObservable().pipe(shareReplay(1));

  // 其余代码不变
}

方案3:强制在变更检测周期内更新状态

通过NgZone.run将状态变更包裹,确保Angular能正确捕获变更:

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

export class LoadingService {
  constructor(private ngZone: NgZone) {}

  public endLoad() {
    this.loadingCount--;
    this.ngZone.run(() => {
      this.loadingSource.next(this.isLoading());
    });
  }

  // 其余代码不变
}

内容的提问来源于stack exchange,提问作者Bail P

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 16:24:13