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

RxJS:如何让两个事件触发时均设置加载状态?

解决方案

方案1:在服务方法中嵌入加载状态通知(组件级独立)

修改服务的getRelatorioCobranca方法,让它在每次发起请求前先发出加载开始的信号,再返回数据。这样无论触发源是selectedDate$还是userObs$,组件都能捕获到加载状态的变化。

服务代码

// 定义状态类型
type RequestState<T> = 
  | { type: 'loading' }
  | { type: 'success', data: T }
  | { type: 'error', error: any };

@Injectable({ providedIn: 'root' })
export class RelatorioService {
  private userObs$ = new BehaviorSubject<User>(null);

  getRelatorioCobranca(date: Date): Observable<RequestState<Relatorio>> {
    return this.userObs$.pipe(
      switchMap(user => {
        if (!user) return of({ type: 'error', error: '用户未登录' });
        
        // 先发出加载开始信号
        return concat(
          of({ type: 'loading' } as RequestState<Relatorio>),
          this.http.get<Relatorio>(`/api/relatorios/cobranca/${date.toISOString()}`, {
            headers: { Authorization: `Bearer ${user.token}` }
          }).pipe(
            map(data => ({ type: 'success', data })),
            catchError(error => of({ type: 'error', error }))
          )
        );
      })
    );
  }
}

组件代码

export class RelatorioComponent {
  selectedDate$ = new BehaviorSubject<Date>(new Date());
  loading$: Observable<boolean>;
  relatorioData$: Observable<Relatorio>;
  error$: Observable<any>;

  constructor(private relatorioService: RelatorioService) {
    const requestState$ = this.selectedDate$.pipe(
      switchMap(date => this.relatorioService.getRelatorioCobranca(date))
    );

    // 拆分状态流
    this.loading$ = requestState$.pipe(
      map(state => state.type === 'loading'),
      distinctUntilChanged()
    );

    this.relatorioData$ = requestState$.pipe(
      filter(state => state.type === 'success'),
      map(state => state.data)
    );

    this.error$ = requestState$.pipe(
      filter(state => state.type === 'error'),
      map(state => state.error)
    );
  }
}

模板使用(async pipe)

<div *ngIf="loading$ | async">加载中...</div>
<div *ngIf="error$ | async as error">出错了:{{ error.message }}</div>
<div *ngIf="relatorioData$ | async as data">
  <!-- 渲染报表数据 -->
</div>

这个方案的好处是:服务封装了用户认证和请求的全部逻辑,组件只需要处理状态拆分,且无论selectedDate$还是userObs$变化,都会触发加载状态。


方案2:封装全局用户流操作符,组件统一处理加载

如果不想在服务中嵌入状态逻辑,可以在服务中封装一个合并用户流的操作符,让组件在统一的触发流上设置加载状态。

服务代码

@Injectable({ providedIn: 'root' })
export class RelatorioService {
  private userObs$ = new BehaviorSubject<User>(null);

  // 封装合并用户流的操作符
  withUser<T>() {
    return (source$: Observable<T>) => combineLatest([source$, this.userObs$]);
  }

  // 仅负责发起请求,需要外部传入用户信息
  fetchRelatorioCobranca(date: Date, user: User): Observable<Relatorio> {
    return this.http.get<Relatorio>(`/api/relatorios/cobranca/${date.toISOString()}`, {
      headers: { Authorization: `Bearer ${user.token}` }
    });
  }
}

组件代码

export class RelatorioComponent {
  selectedDate$ = new BehaviorSubject<Date>(new Date());
  loading = false;
  relatorioData$: Observable<Relatorio>;

  constructor(private relatorioService: RelatorioService) {
    this.relatorioData$ = this.selectedDate$.pipe(
      this.relatorioService.withUser(),
      tap(() => this.loading = true), // 无论哪个流变化,都会触发加载开始
      switchMap(([date, user]) => {
        return this.relatorioService.fetchRelatorioCobranca(date, user).pipe(
          finalize(() => this.loading = false), // 请求完成(成功/失败)都结束加载
          catchError(error => {
            // 处理错误
            return throwError(error);
          })
        );
      })
    );
  }
}

这个方案中,withUser操作符封装了合并用户流的逻辑,避免了多组件重复写combineLatest,同时组件的加载状态在合并后的流上触发,确保两个源变化都能触发加载。


方案3:服务提供独立的加载状态流(需注意组件隔离)

如果希望服务统一管理加载状态,可以在服务中创建一个loading$流,每次发起请求时更新它。但要注意多组件调用时的状态冲突,建议为每个请求实例生成独立的加载流,或者用请求ID区分。

服务代码

@Injectable({ providedIn: 'root' })
export class RelatorioService {
  private userObs$ = new BehaviorSubject<User>(null);
  private loadingSubject = new BehaviorSubject<boolean>(false);
  readonly loading$ = this.loadingSubject.asObservable();

  getRelatorioCobranca(date: Date): Observable<Relatorio> {
    return this.userObs$.pipe(
      switchMap(user => {
        this.loadingSubject.next(true);
        return this.http.get<Relatorio>(`/api/relatorios/cobranca/${date.toISOString()}`, {
          headers: { Authorization: `Bearer ${user.token}` }
        }).pipe(
          finalize(() => this.loadingSubject.next(false))
        );
      })
    );
  }
}

组件代码

export class RelatorioComponent {
  selectedDate$ = new BehaviorSubject<Date>(new Date());
  relatorioData$: Observable<Relatorio>;
  loading$: Observable<boolean>;

  constructor(private relatorioService: RelatorioService) {
    this.relatorioData$ = this.selectedDate$.pipe(
      switchMap(date => this.relatorioService.getRelatorioCobranca(date))
    );
    this.loading$ = this.relatorioService.loading$;
  }
}

注意:这个方案的loading$是服务全局的,如果多个组件同时调用getRelatorioCobranca,加载状态会互相影响。如果需要组件隔离,建议让服务的方法返回包含加载状态的流(参考方案1)。


总结

优先推荐方案1,它既封装了服务端的请求逻辑,又能让组件清晰处理不同状态,且完全避免了重复代码;如果希望状态逻辑完全在组件侧处理,选择方案2更灵活;方案3适合单组件场景,多组件使用需谨慎。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 03:00:09