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')
问题原因及修复方案
问题根源
- 弹窗数据传递不完整:详情页打开弹窗时仅传递了
productID,未传递完整的商品对象product和当前登录用户的userID,导致弹窗组件中data.product和data.userID均为undefined。 - 参数引用错误:弹窗按钮点击时尝试访问不存在的
data.product属性,进而访问product.quantity时触发未定义错误。
修复步骤
- 获取当前登录用户ID:在详情页组件中注入
AngularFireAuth,订阅authState获取当前用户的UID。 - 传递完整数据到弹窗:打开弹窗前先订阅商品Observable获取完整商品对象,再将商品对象和用户ID一起传入弹窗。
- 修正弹窗组件逻辑:弹窗中直接使用注入的
data对象,避免传递未定义参数,同时复制商品对象防止修改原数据。 - 完善数量处理:处理商品数量可能为
undefined的情况,默认设为0后再加1。
内容的提问来源于stack exchange,提问作者Stéphane Konan
相关产品推荐
相关产品推荐

