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

Angular结合Firestore实现商品添加至购物车的问题求助

问题:商品添加至Firestore用户购物车子集合失败

我已实现通过URL获取商品数据,在商品详情页设有按钮,希望将商品添加至登录用户的Firestore子集合users/{userID}/shopping/{productID}中,但点击确认添加按钮时控制台报错。

相关代码

商品详情页模板(details-product.component.html)

<div class="uk-container panier" *ngIf="product">
    <div id="modal-center" class="uk-flex-top" uk-modal>
      <div class="uk-modal-dialog uk-modal-body uk-margin-auto-vertical">
          <button class="uk-modal-close-default" type="button" uk-close></button>
          <img [src]="(product | async)?.imageURL" class="nav-left">
      </div>
    </div>
    <div class="uk-child-width-1-2@s" uk-grid>
      <div>
        <a href="#modal-center" uk-toggle>
          <img [src]="(product | async)?.imageURL" class="nav-left" alt="">
        </a>
      </div>
      <div>
        <div class="uk-dark uk-padding">
          <h3> {{ (product | async)?.name }}</h3>
          <hr class="uk-divider-icon">
          <div class="uk-flex prix">
            <p class="uk-text">
              <span>{{ (product | async)?.price | currency: 'XOF' }} </span>
            </p>
          </div>
          <hr class="uk-divider-icon">
          <button class="uk-button uk-button-default uk-form-width-medium" (click)="showDialog()">
           <i class='bx bxs-cart-add'></i>
            J'achète le produit
          </button>
        </div>
      </div>
    </div>
</div>

商品详情页截图

购物车服务(shopping-cart.service.ts)

import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/compat/firestore';
import "firebase/auth";

import { User } from 'src/app/models/user.model';
import { Product } from 'src/app/models/product.model';

@Injectable({
  providedIn: 'root'
})
export class ShoppingCardService {

  userCollection: AngularFirestoreCollection<User>;
  productsCollection: AngularFirestoreCollection<Product>;

  constructor(private readonly dbstore: AngularFirestore) {
    this.userCollection = this.dbstore.collection('users');
    this.productsCollection = this.dbstore.collection('products');
  }

  addToMyCart(product: Product, userID: string, qteProduct: number) {
    const userDoc = this.dbstore.firestore.collection('users').doc(userID);
    const productDoc = this.dbstore.firestore.collection('products').doc(product.id);
    const userShoppingProduct = userDoc.collection('shopping');
    return userShoppingProduct.doc(product.id).set(product);
  }
}

商品服务(product.service.ts)

import { Injectable } from '@angular/core';
import { AngularFirestore, AngularFirestoreCollection } from '@angular/fire/compat/firestore';
import { Router } from '@angular/router';
import { Observable } from 'rxjs';
import { Product } from 'src/app/models/product.model';

@Injectable({
  providedIn: 'root'
})
export class ProductsService {

    productCollection: AngularFirestoreCollection<Product>;
    
    constructor(private dbstore: AngularFirestore, private router: Router) {
      this.productCollection = this.dbstore.collection('products', (ref) => 
        ref.orderBy('category', 'desc')
      );
    }
    getDetailProduct(productId: string): Observable<any> {
      return this.productCollection.doc(productId).valueChanges();
    }
}

商品详情页逻辑(details-product.component.ts)

import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs/internal/Observable';
import { User } from 'src/app/models/user.model';
import { Product } from 'src/app/models/product.model';
import { ProductsService } from '../../shared/services/products.service';
import { ShoppingCardService } from 'src/app/shared/services/shopping-card.service';
import { AngularFirestoreCollection } from '@angular/fire/compat/firestore';
import { AddToCartComponent } from './add-to-cart.component'
import { MatDialog } from '@angular/material/dialog';
import { AngularFireAuth } from '@angular/fire/compat/auth'; // 新增:导入用户认证服务

@Component({
  selector: 'app-details-product',
  templateUrl: './details-product.component.html',
  styleUrls: ['./details-product.component.css']
})
export class DetailsProductComponent implements OnInit {
  
  quantity: number = 0;
  productIdRoute: string;
  isMyProduct: boolean = false;
  product: Observable<Product>;
  userCollection!: AngularFirestoreCollection<User>
  currentUserID: string | null = null;

  constructor(
    private route: ActivatedRoute,
    private productService: ProductsService,
    private shoppingCardService: ShoppingCardService,
    private titleService: Title,
    private dialog: MatDialog,
    private afAuth: AngularFireAuth // 新增:注入用户认证服务
  ) {
    const routeParams = this.route.snapshot.paramMap;
    this.productIdRoute = String(routeParams.get('productId'));
    this.product = this.productService.getDetailProduct(this.productIdRoute);
    // 获取当前登录用户ID
    this.afAuth.authState.subscribe(user => {
      this.currentUserID = user?.uid || null;
    });
  }

  showDialog(): void {
    // 订阅商品流获取完整商品对象
    this.product.subscribe(productData => {
      if (productData && this.currentUserID) {
        this.dialog.open(AddToCartComponent, {
          width: '30rem',
          data: { product: productData, userID: this.currentUserID },
        });
      }
    });
  }

确认添加弹窗组件(add-to-cart.component.ts)

import { Component, OnInit, Inject } from '@angular/core';
import { MatDialog, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { ShoppingCardService } from '../../shared/services/shopping-card.service';
import { Product } from 'src/app/models/product.model';


@Component({
  selector: 'app-add-to-cart',
  template: `
    <div class="relative p-4 w-full max-w-md h-full md:h-auto">
            <div class="p-6 text-center">
                <svg aria-hidden="true" class="mx-auto mb-4 w-14 h-14 text-gray-400 dark:text-gray-200" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
                <h3 class="mb-5 text-lg font-normal text-gray-500 dark:text-gray-400">Do you want to add this product?</h3>
                <button (click)="onAddPubpik()" type="button" class="text-white bg-gray-800 hover:bg-gray-800 focus:ring-4 focus:outline-none font-medium rounded-lg text-sm inline-flex items-center px-5 py-2.5 text-center mr-2">
                   Yes, i add to cart
                </button>
            </div>
    </div>
  `,
  styleUrls: ['./details-product.component.css']
})
export class AddToCartComponent implements OnInit {

  constructor(
    @Inject(MAT_DIALOG_DATA) public data: { product: Product, userID: string },
    private dialog: MatDialog,
    private shoppingService: ShoppingCardService
  ) {}

  ngOnInit(): void {
  }

  async onAddPubpik(): Promise<void> {
    this.dialog.closeAll();
    // 复制商品对象,避免修改原数据源
    const product = {...this.data.product};
    // 处理数量可能为undefined的情况
    product.quantity = (product.quantity || 0) + 1;
    product.isMyProduct = true;
    await this.shoppingService.addToMyCart(product, this.data.userID, product.quantity);
  }

}

确认弹窗截图

报错信息

控制台报错内容:

TypeError: 无法读取未定义的属性(读取'quantity')

问题原因及修复方案

问题根源

  1. 弹窗数据传递不完整:详情页打开弹窗时仅传递了productID,未传递完整的商品对象product和当前登录用户的userID,导致弹窗组件中data.product和data.userID均为undefined。
  2. 参数引用错误:弹窗按钮点击时尝试访问不存在的data.product属性,进而访问product.quantity时触发未定义错误。

修复步骤

  1. 获取当前登录用户ID:在详情页组件中注入AngularFireAuth,订阅authState获取当前用户的UID。
  2. 传递完整数据到弹窗:打开弹窗前先订阅商品Observable获取完整商品对象,再将商品对象和用户ID一起传入弹窗。
  3. 修正弹窗组件逻辑:弹窗中直接使用注入的data对象,避免传递未定义参数,同时复制商品对象防止修改原数据。
  4. 完善数量处理:处理商品数量可能为undefined的情况,默认设为0后再加1。

内容的提问来源于stack exchange,提问作者Stéphane Konan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 11:50:24