如何在NgRx Effects中实现多后续API调用并存储原数据
解决方案
要实现同时存储原始字符串数组、并为每个元素发起API请求保存响应的需求,你需要分三步修改NgRx代码:扩展Action定义、更新Effects逻辑、编写对应的Reducer处理状态更新。
1. 扩展Action类型
首先在data.actions.ts中新增所需的Action,分别用于保存原始数组、单个元素请求成功/失败:
// data.actions.ts import { createAction, props } from '@ngrx/store'; // 触发数据加载的初始动作(保留原有逻辑) export const loadData = createAction('[Data] Load Data', props<{ payload: string }>()); // 保存原始字符串数组的动作 export const loadRawDataSuccess = createAction('[Data] Load Raw Data Success', props<{ rawData: string[] }>()); // 单个元素API请求成功的动作 export const loadItemSuccess = createAction('[Data] Load Item Success', props<{ itemId: string; itemData: any }>()); // 单个元素API请求失败的动作 export const loadItemFailure = createAction('[Data] Load Item Failure', props<{ itemId: string; error: string }>()); // 全局加载失败动作(保留原有逻辑) export const loadDataFailure = createAction('[Data] Load Data Failure', props<{ error: string }>());
2. 更新Effects逻辑
修改DataEffects,在拿到初始数组后先派发保存原始数据的Action,再遍历每个元素发起独立API请求:
import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { HttpClient } from '@angular/common/http'; import { switchMap, map, catchError, mergeMap, tap } from 'rxjs/operators'; import { of } from 'rxjs'; import { loadData, loadRawDataSuccess, loadItemSuccess, loadItemFailure, loadDataFailure } from './data.actions'; @Injectable() export class DataEffects { loadData$ = createEffect(() => this.actions$.pipe( ofType(loadData), switchMap((action) => this.http.get<string[]>(`/api/data/${action.payload}`).pipe( // 第一步:派发Action保存原始字符串数组到Store tap((rawData) => this.actions$.next(loadRawDataSuccess({ rawData }))), // 第二步:遍历数组每个元素,发起单个API请求 mergeMap((rawData) => rawData.map((itemId) => this.http.get(`/api/items/${itemId}`).pipe( map((itemData) => loadItemSuccess({ itemId, itemData })), catchError((error) => of(loadItemFailure({ itemId, error: error.message }))) ) ) ), // 捕获初始数据加载的全局错误 catchError((error) => of(loadDataFailure({ error: error.message }))) ) ) ) ); constructor(private actions$: Actions, private http: HttpClient) {} }
关键逻辑说明:
- 使用
tap操作符在获取初始数组后立即派发loadRawDataSuccess,确保原始字符串数组被存入Store。 - 使用
mergeMap遍历数组元素,为每个元素发起独立的API请求,每个请求的结果会触发对应的成功/失败Action。 - 单个元素的请求错误不会中断整个流程,每个错误都会被捕获并派发
loadItemFailure。
3. 编写Reducer处理状态更新
在data.reducer.ts中定义状态结构,并处理所有新增的Action:
// data.reducer.ts import { createReducer, on } from '@ngrx/store'; import { loadRawDataSuccess, loadItemSuccess, loadItemFailure, loadDataFailure } from './data.actions'; export interface DataState { rawData: string[]; // 存储原始字符串数组 items: Record<string, any>; // 存储单个元素的API响应,key为元素ID itemErrors: Record<string, string>; // 存储单个元素的请求错误 globalError: string | null; // 存储初始数据加载的全局错误 } export const initialState: DataState = { rawData: [], items: {}, itemErrors: {}, globalError: null }; export const dataReducer = createReducer( initialState, // 处理原始数组保存 on(loadRawDataSuccess, (state, { rawData }) => ({ ...state, rawData, globalError: null })), // 处理单个元素请求成功 on(loadItemSuccess, (state, { itemId, itemData }) => ({ ...state, items: { ...state.items, [itemId]: itemData }, itemErrors: { ...state.itemErrors, [itemId]: undefined } // 清除该元素的历史错误 })), // 处理单个元素请求失败 on(loadItemFailure, (state, { itemId, error }) => ({ ...state, itemErrors: { ...state.itemErrors, [itemId]: error } })), // 处理全局加载失败 on(loadDataFailure, (state, { error }) => ({ ...state, globalError: error })) );
这样就能同时满足两个需求:原始字符串数组被存入rawData字段,每个元素的API响应会被存入items对象中,错误信息分别存储在对应字段里。
内容的提问来源于stack exchange,提问作者Navenkumar
相关产品推荐
相关产品推荐

