如何从API数据中提取products数组并结合NgRx在前端展示?
解决方案:提取API返回的products数组(NgRx场景)
根据你的需求,下面提供三种不同层级的处理方案,你可以根据实际业务场景选择:
方案1:在Effects中提前提取(推荐)
如果你的状态只需要products数组,不需要保留完整的API响应,直接在Effect里处理最合理,能简化后续Reducer和组件的逻辑。
修改Effect代码
在API请求的map操作中直接提取data.products:
import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { of } from 'rxjs'; import { catchError, map, mergeMap } from 'rxjs/operators'; import { ProductService } from './product.service'; import * as ProductActions from './product.actions'; @Injectable() export class ProductEffects { loadProducts$ = createEffect(() => this.actions$.pipe( ofType(ProductActions.loadProducts), mergeMap(() => this.productService.getProducts().pipe( // 直接提取data中的products数组 map(apiRes => ProductActions.loadProductsSuccess({ products: apiRes.data.products })), catchError(err => of(ProductActions.loadProductsFailure({ error: err }))) ) ) ) ); constructor(private actions$: Actions, private productService: ProductService) {} }
对应Action定义
调整成功Action的参数,直接接收products数组:
import { createAction, props } from '@ngrx/store'; import { IProduct } from './product.model'; export const loadProducts = createAction('[Product] Load Products'); export const loadProductsSuccess = createAction( '[Product] Load Products Success', props<{ products: IProduct[] }>() ); export const loadProductsFailure = createAction( '[Product] Load Products Failure', props<{ error: any }>() );
Reducer代码
Reducer直接将products存入状态:
import { createReducer, on } from '@ngrx/store'; import * as ProductActions from './product.actions'; import { IProduct } from './product.model'; export interface ProductState { products: IProduct[]; loading: boolean; error: any; } const initialState: ProductState = { products: [], loading: false, error: null }; export const productReducer = createReducer( initialState, on(ProductActions.loadProducts, state => ({ ...state, loading: true, error: null })), on(ProductActions.loadProductsSuccess, (state, { products }) => ({ ...state, loading: false, products: products })), on(ProductActions.loadProductsFailure, (state, { error }) => ({ ...state, loading: false, error: error })) );
组件中使用
直接从Store中选择products数组即可循环展示:
import { Component, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; import { IProduct } from './product.model'; import * as fromProduct from './product.reducer'; import * as ProductActions from './product.actions'; @Component({ selector: 'app-product-list', template: ` <div *ngFor="let product of products$ | async"> <h3>{{ product.name }}</h3> <p>价格:{{ product.price }}</p> </div> ` }) export class ProductListComponent implements OnInit { products$: Observable<IProduct[]>; constructor(private store: Store<fromProduct.ProductState>) { this.products$ = this.store.select(fromProduct.selectProducts); } ngOnInit(): void { this.store.dispatch(ProductActions.loadProducts()); } }
(注:需要在reducer中定义selectProducts选择器:export const selectProducts = (state: ProductState) => state.products;)
方案2:在Reducer中提取
如果需要保留完整的API响应(比如要用到status等其他字段),可以在Reducer中提取products数组。
Effect代码(返回完整响应)
// Effect中返回整个API响应 map(apiRes => ProductActions.loadProductsSuccess({ response: apiRes }))
Action定义
export const loadProductsSuccess = createAction( '[Product] Load Products Success', props<{ response: any }>() // 建议定义ApiResponse接口替代any );
Reducer代码(提取products)
export interface ProductState { products: IProduct[]; rawApiResponse: any; // 存储完整API响应 loading: boolean; error: any; } const initialState: ProductState = { products: [], rawApiResponse: null, loading: false, error: null }; export const productReducer = createReducer( initialState, on(ProductActions.loadProducts, state => ({ ...state, loading: true, error: null })), on(ProductActions.loadProductsSuccess, (state, { response }) => ({ ...state, loading: false, rawApiResponse: response, products: response.data.products // 在这里提取products数组 })), on(ProductActions.loadProductsFailure, (state, { error }) => ({ ...state, loading: false, error: error })) );
组件中依然可以直接使用selectProducts选择器获取数组。
方案3:在组件中提取(不推荐)
如果只是临时需求,也可以在组件的store.select后通过map操作提取,但不建议这么做——组件应该只负责展示,数据转换逻辑尽量放在NgRx层,保持组件简洁。
示例代码:
// 组件中 import { map } from 'rxjs/operators'; // 假设reducer中存储了完整的API响应,并有对应的选择器 this.products$ = this.store.select(fromProduct.selectRawApiResponse).pipe( map(res => res?.data?.products || []) // 处理空值情况 );
总结
- 优先选方案1:逻辑更清晰,状态只存储必要数据,减少冗余。
- 需要保留完整API响应时选方案2:兼顾数据提取和原始响应存储。
- 方案3尽量避免:会让组件承担额外的数据处理职责,不符合NgRx的设计理念。
内容的提问来源于stack exchange,提问作者Youzef
相关产品推荐
相关产品推荐

