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
相关产品推荐
相关产品推荐

