Angular12+NGRX场景下如何合并多个返回Observable的API调用为单一interface数组
解决方案
问题根因
- 你当前的
getUtilities方法里用了switchMap处理forkJoin的返回结果:switchMap会把你返回的数组自动拆分为逐个元素发射,所以你有4条房产数据就会触发4次成功action,这就是action多触发的核心原因,同时这也导致最终返回的是逐个发射的对象而非你需要的数组。 - 重复调用后端接口是因为没有对Observable请求结果做缓存,每次订阅都会触发新的HTTP请求。
修正后的代码
1. 优化API调用添加缓存
给所有API方法添加shareReplay操作符缓存结果,避免重复发起请求:
import { shareReplay } from 'rxjs/operators'; getActiveProperties(): Observable<Property[]> { return this.http.get<Property[]>(this.getPropertiesUrl).pipe( shareReplay(1) // 缓存1份结果,后续订阅直接复用 ); } getCurrentUtilities( currentMonth: string, currentYear: string ): Observable<utilities[]> { return this.http .get<utilities[]>( `${this.getUtiltiesUrl}?month=${currentMonth}&year=${currentYear}` ) .pipe( shareReplay(1) ); } getPreviousUtilities( previousMonth: string, previousYear: string ): Observable<utilitiesPrevious[]> { return this.http .get<utilities[]>( `${this.getUtiltiesUrl}?month=${previousMonth}&year=${previousYear}` ) .pipe( map((response) => response.map( (sp): utilitiesPrevious => ({ propertyID: sp.propertyID, cold1Prev: sp.cold1, cold2Prev: sp.cold2, hot1Prev: sp.hot1, hot2Prev: sp.hot2, gasPrev: sp.gas, electricityPrev: sp.electricity, }) ) ), shareReplay(1) ); } getUtilityComments(): Observable<utilitiesComments[]> { return this.http.get<utilitiesComments[]>(`${this.getCommentsUrl}`).pipe( shareReplay(1) ); }
2. 修正合并逻辑
把switchMap替换为map,同时优化合并逻辑不需要多次遍历数组,单次遍历即可完成所有字段合并:
getUtilities( currentMonth: string, currentYear: string, previousMonth: string, previousYear: string ): Observable<utilitiesList[]> { const properties = this.getActiveProperties(); const currentUtilities = this.getCurrentUtilities( currentMonth, currentYear ); const previousUtilities = this.getPreviousUtilities( previousMonth, previousYear ); const comments = this.getUtilityComments(); return forkJoin([properties, currentUtilities, previousUtilities, comments]) .pipe( map(([properties, currentUtils, prevUtils, comments]) => { // 先把其他数据集转成propertyID为key的Map,提升查找效率 const currentMap = new Map(currentUtils.map(item => [item.propertyID, item])); const prevMap = new Map(prevUtils.map(item => [item.propertyID, item])); const commentMap = new Map(comments.map(item => [item.propertyID, item])); // 单次遍历完成合并 return properties.map(prop => ({ ...prop, ...(currentMap.get(prop.propertyID) || {}), ...(prevMap.get(prop.propertyID) || {}), ...(commentMap.get(prop.propertyID) || {}) })) as utilitiesList[]; }) ); }
NGRX侧优化(可选,进一步避免重复请求)
在处理数据加载的Effect中添加判断,先查询Store中是否已经存在utilitiesList数据,如果已经有数据就不再发起API请求:
loadUtilities$ = createEffect(() => this.actions$.pipe( ofType(loadUtilities), withLatestFrom(this.store.select(selectUtilitiesList)), filter(([_, utilities]) => !utilities?.length), // 已有数据则跳过请求 switchMap(([action]) => this.dataService.getUtilities(action.currentMonth, action.currentYear, action.previousMonth, action.previousYear).pipe( map(utilities => loadUtilitiesSuccess({ utilities })), catchError(error => of(loadUtilitiesFailure({ error }))) ) ) ) );
内容的提问来源于stack exchange,提问作者Aivis Šteinfelds
相关产品推荐
相关产品推荐

