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

Angular12+NGRX场景下如何合并多个返回Observable的API调用为单一interface数组

解决方案

问题根因

  1. 你当前的getUtilities方法里用了switchMap处理forkJoin的返回结果:switchMap会把你返回的数组自动拆分为逐个元素发射,所以你有4条房产数据就会触发4次成功action,这就是action多触发的核心原因,同时这也导致最终返回的是逐个发射的对象而非你需要的数组。
  2. 重复调用后端接口是因为没有对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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.07 19:00:00