如何在NgRx中通过ID获取商品并更新购物车
解决NgRx购物车仅存ID并动态获取商品数据的问题
核心调整思路
按照"仅存商品ID和数量到Store,通过ID从后端获取完整商品信息"的要求,我们需要调整动作定义、Reducer逻辑、Effects流程,并修改组件的数据获取方式。
1. 修正Cart Actions定义
修改cart.actions.ts,让addItem仅传递商品ID(而非完整对象),并完善加载相关动作:
import { createAction, props } from '@ngrx/store'; import { CartItem, Product } from 'src/app/types'; // 用户触发添加商品,仅传ID export const addItem = createAction( '[Cart Page] Add Item', props<{ productId: number }>() ); export const removeItem = createAction( '[Cart Page] Remove Item', props<{ productId: number }>() ); export const updateItem = createAction( '[Cart Page] Edit Item', props<{ productId: number; quantity: number }>() ); // 加载商品成功动作,携带完整商品数据 export const loadItemSuccess = createAction( '[Cart Page] Load Item Success', props<{ product: Product }>() ); // 可选:添加加载失败动作,用于错误处理 export const loadItemFailure = createAction( '[Cart Page] Load Item Failure', props<{ error: string }>() );
2. 更新Cart Reducer处理加载成功动作
修改cart.reducer.ts,添加对loadItemSuccess的处理逻辑,将获取到的完整商品转换为CartItem加入购物车:
import { createReducer, createSelector, on } from '@ngrx/store'; import { CartItem, Product } from 'src/app/types'; import { addItem, removeItem, updateItem, loadItemSuccess } from './cart.actions'; export interface AppState { cart: CartState; } export interface CartState { cart: CartItem[]; error: string | null; status: 'pending' | 'loading' | 'error' | 'success'; } export const initialState: CartState = { cart: [], error: null, status: 'pending', }; export const cartReducer = createReducer( initialState, // 处理加载成功的商品,转换为CartItem加入购物车 on(loadItemSuccess, (state, { product }) => { const existingItem = state.cart.find(item => item.id === product.id); if (existingItem) { // 商品已存在,更新数量 return { ...state, cart: state.cart.map(item => item.id === product.id ? { ...item, quantity: item.quantity + 1 } : item ), status: 'success' }; } // 商品不存在,新增CartItem return { ...state, cart: [...state.cart, { id: product.id, quantity: 1 }], status: 'success' }; }), on(removeItem, (state, { productId }) => ({ ...state, cart: state.cart.filter(item => item.id !== productId), })), on(updateItem, (state, { productId, quantity }) => { let shouldRemove = false; const updatedCart = state.cart.map(item => { if (item.id === productId) { const newQuantity = item.quantity + quantity; if (newQuantity <= 0) { shouldRemove = true; return item; } return { ...item, quantity: newQuantity }; } return item; }); return { ...state, cart: shouldRemove ? updatedCart.filter(item => item.id !== productId) : updatedCart, }; }), // 可选:处理加载失败动作 on(/* loadItemFailure */, (state, { error }) => ({ ...state, error, status: 'error' })) ); export const selectCartState = (state: AppState) => state.cart; export const selectCart = createSelector( selectCartState, (state: CartState) => state.cart );
3. 完善Cart Effects逻辑
调整CartEffects,确保监听的addItem动作参数匹配,并添加错误处理:
import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { of } from 'rxjs'; import { map, exhaustMap, catchError } from 'rxjs/operators'; import { DataService } from 'src/app/data.service'; import * as CartActions from './cart.actions'; @Injectable() export class CartEffects { loadProduct$ = createEffect(() => this.actions$.pipe( ofType(CartActions.addItem), exhaustMap(({ productId }) => this.dataService.getProductById(productId).pipe( map(product => CartActions.loadItemSuccess({ product })), catchError(error => of(CartActions.loadItemFailure({ error: error.message }))) ) ) ) ); constructor(private actions$: Actions, private dataService: DataService) {} }
4. 修改组件:从Store获取CartItem并关联完整商品数据
在OutlineComponent中,将CartItem的ID转换为完整Product信息,这里提供两种实现方式:
方式一:组件内直接关联API请求(适合简单场景)
import { Component } from '@angular/core'; import { Observable, combineLatest, map, of, switchMap } from 'rxjs'; import { Store } from '@ngrx/store'; import { AppState } from '../state/cart/cart.reducer'; import { selectCart } from '../state/cart/cart.reducer'; import { CartItem, Product } from '../types'; import { AsyncPipe, NgFor, NgIf } from '@angular/common'; import { removeItem, updateItem, addItem } from '../state/cart/cart.actions'; import { DataService } from '../data.service'; @Component({ standalone: true, imports: [NgIf, NgFor, AsyncPipe], selector: 'app-outline', templateUrl: './outline.component.html', styleUrls: ['./outline.component.css'], }) export class OutlineComponent { // 获取购物车的ID和数量数组 cartItems$: Observable<CartItem[]>; // 转换为包含完整商品信息的数组 cartWithProducts$: Observable<(CartItem & { product: Product })[]>; cartSize$: Observable<number>; showFiller = false; constructor( private store: Store<AppState>, private dataService: DataService ) { this.cartItems$ = this.store.pipe(select(selectCart)); // 组合购物车数据和商品详情请求 this.cartWithProducts$ = this.cartItems$.pipe( map(items => items.map(item => combineLatest([ of(item), this.dataService.getProductById(item.id) ]).pipe( map(([cartItem, product]) => ({ ...cartItem, product })) ) )), switchMap(observables => combineLatest(observables)) ); this.cartSize$ = this.cartItems$.pipe( map(items => items.reduce((total, { quantity }) => total + quantity, 0)) ); } // 修改addItem触发逻辑,现在只传ID addToCart(productId: number) { this.store.dispatch(addItem({ productId })); } removeCartItem(productId: number) { this.store.dispatch(removeItem({ productId })); } addOneItem(productId: number) { this.store.dispatch(updateItem({ productId, quantity: 1 })); } removeOneItem(productId: number) { this.store.dispatch(updateItem({ productId, quantity: -1 })); } }
方式二:Store缓存商品数据(推荐,避免重复请求)
如果希望避免重复请求API,可以新增一个products State来缓存已获取的商品:
- 创建
products.reducer.ts维护商品缓存 - 在Effects中,获取商品后不仅更新购物车,同时更新商品缓存
- 通过Selector组合购物车数据和缓存的商品数据
这种方式更适合复杂应用,减少不必要的API调用。
5. 模板调整(示例)
在组件模板中,使用cartWithProducts$展示完整商品信息:
<div *ngIf="cartWithProducts$ | async as cart"> <div *ngFor="let item of cart"> <h3>{{ item.product.title }}</h3> <p>价格: {{ item.product.price }}</p> <p>数量: {{ item.quantity }}</p> <button (click)="removeOneItem(item.id)">-</button> <button (click)="addOneItem(item.id)">+</button> <button (click)="removeCartItem(item.id)">删除</button> </div> <p>购物车总数量: {{ cartSize$ | async }}</p> </div>
内容的提问来源于stack exchange,提问作者Mathew
相关产品推荐
相关产品推荐

