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

如何在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来缓存已获取的商品:

  1. 创建products.reducer.ts维护商品缓存
  2. 在Effects中,获取商品后不仅更新购物车,同时更新商品缓存
  3. 通过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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 23:18:11