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

Angular懒加载Shop模块时BehaviorSubject值无法被组件识别的问题

Angular懒加载模块下购物车服务状态丢失问题

我用Angular实现购物车功能,商品可通过购物车页面或商品详情页的「ADD TO CART」按钮加入购物车(两者共用同一个ShoppingCartService)。预加载ShoppingCartModule和包含商品详情组件的ShopModule时功能正常,但懒加载ShopModule时,商品详情组件调用ShoppingCartService无法获取BehaviorSubject保存的现有购物车值,会重新初始化购物车而非更新现有购物车。


购物车服务(ShoppingCartService)

import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { BehaviorSubject } from "rxjs";
import { take } from "rxjs/operators";
import { ICartItem } from "../../../app/shared/Interfaces/ICartItem";
import { IOrderSummary } from "../../../app/shared/Interfaces/IOrderSummary";
import { IProduct } from "../../../app/shared/Interfaces/IProduct";
import { ShoppingCart } from "../../../app/shared/Models/ShoppingCart";


@Injectable()
export class ShoppingCartService {

    baseUrl = "https://localhost:44330/api/";
   
    constructor(private http:HttpClient) {
  
    }

//shoppingCart Observable
private _shoppingCartObservable = new BehaviorSubject<ShoppingCart | null>(null);
public shoppingCartObservable$ = this._shoppingCartObservable.asObservable();

//orderSummaryObservable
private _orderSummaryObservable = new BehaviorSubject<IOrderSummary | null>(null);
public orderSummaryObservable$ = this._orderSummaryObservable .asObservable();


getShoppingCart(shoppingCartId:string){
this.http.get<ShoppingCart>(this.baseUrl+"GetShoppingCartById?id="+shoppingCartId).subscribe(
{next : shoppingCart =>{this._shoppingCartObservable.next(shoppingCart);
this.setOrderSummary(shoppingCart);
} })
}

setShoppingCart(shoppingCart:ShoppingCart){
    this.http.post<ShoppingCart>(this.baseUrl+"UpdateShoppingCart", shoppingCart).subscribe(
    {next : shoppingCart => {this._shoppingCartObservable.next(shoppingCart)
        this.setOrderSummary(shoppingCart);
    }
    }
    )}
    
getCurrentShoppingCart(){
    return this._shoppingCartObservable.value;    //.value returns the last/current value of the Behavioral Event 
}


 addItemToCart(item: IProduct | ICartItem , qty:number=1){
   
    if ((item as ICartItem).brand ===undefined)
           item = this.mapProductToCartItem( (item as IProduct) );
     
           
    var shoppingCart = this.getCurrentShoppingCart();


        if(shoppingCart==null){
            shoppingCart = this.createShoppingCart();
        }

        shoppingCart.items = this.AddOrUpdateCartItem(shoppingCart.items, item as ICartItem, qty);
        this.setShoppingCart(shoppingCart);
    }


removeFromCart(itemId:number, qty:number=1){

let shoppingCart = this.getCurrentShoppingCart();

if(shoppingCart==null) return;

let itemToRemove = shoppingCart.items.find(i=>i.id===itemId);

if(itemToRemove){
itemToRemove.qty -= qty;

if(itemToRemove.qty <=0)
shoppingCart.items = shoppingCart.items.filter(i=>i.id !== itemId);

}

if(shoppingCart.items.length <=0){
    this.removeShoppingCart(shoppingCart)
}else{
    this.setShoppingCart(shoppingCart);
}

}
   
    
    //private helper methods
    private mapProductToCartItem(item:IProduct):ICartItem{

        return { id : item.id,
            productName: item.productName,
            price: item.salesPrice,
            qty: 0,
            pictureUrl:item.pictureUrl,
            brand: item.brandName,
            type: item.categoryName      
        };
    };

    private createShoppingCart(){
        const shoppingCart = new ShoppingCart();
        localStorage.setItem("shoppingCartId",shoppingCart.id);
        return shoppingCart;
    }

    private AddOrUpdateCartItem(shoppingCartItems:ICartItem[], cartItem:ICartItem, qty:number){

    const cartItemToAddOrUpdate  = shoppingCartItems.find(i=>i.id===cartItem.id);   

    if(cartItemToAddOrUpdate){
        cartItemToAddOrUpdate.qty += qty;
    }
    else{
        cartItem.qty = qty;
        shoppingCartItems.push(cartItem);
    }

return shoppingCartItems;

    }

private setOrderSummary(shoppingCart:ShoppingCart){

        if(!shoppingCart)
                return;
        
                let totalqty = 0;
                let totalPrice = 0;
                let shipping = 0;
        
                shoppingCart.items.forEach(item=>{
                    totalqty += item.qty;
                    totalPrice += item.qty * item.price;
                } )
        
        let orderSummary:IOrderSummary = {
            shipping: shipping,
            totalPrice: totalPrice,
            subTotal: totalPrice + shipping,
            totalQty : totalqty
        }
        
        this._orderSummaryObservable.next(orderSummary);        
        }
        
private removeShoppingCart(shoppingCart:ShoppingCart){
this.http.delete(this.baseUrl+"DeleteShoppingCart?id="+shoppingCart.id);
this._shoppingCartObservable.next(null);
this._orderSummaryObservable.next(null);
localStorage.removeItem("shoppingCartId")

}

}

商品详情组件(ProductDetailsComponent)

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
import { IProduct } from 'src/app/shared/Interfaces/IProduct';
import { ProductService } from 'src/app/core/Services/ProductService';
import { ShoppingCartService } from 'src/app/core/Services/ShoppingCartService';

@Component({
  selector: 'app-product-details',
  templateUrl: './product-details.component.html',
  styleUrls: ['./product-details.component.scss']
})
export class ProductDetailsComponent implements OnInit {

  product:IProduct;
  productId:number;

  constructor(private route:ActivatedRoute,
    private _productsService:ProductService,
    private _shoppingCartService:ShoppingCartService) { }

  ngOnInit(): void {

    this.route.params.subscribe( (params:Params)=>{
    this.productId = params['id'];
    })

    this.getProduct();

  }

  getProduct(){
    this._productsService.getProductById(this.productId).subscribe((data)=>{
this.product = data;
    })
  }

  addToCart(){

   if(this.product){
    this._shoppingCartService.addItemToCart(this.product);
    console.log('added from productdetails');
  }

  }

}

Shop模块(ShopModule)

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ProductDetailsComponent } from './product-details/product-details.component';
import { ProductItemComponent } from './product-item/product-item.component';
import { ShopPageComponent } from './shop-page/shop-page.component';
import { SHAREDModule } from '../shared/shared.module';
import { RouterModule, Routes } from '@angular/router';


const routes:Routes=[
  {path:'',component:ShopPageComponent},
  {path:':id',component:ProductDetailsComponent}
  ]

@NgModule({
  declarations: [ProductDetailsComponent, ProductItemComponent, ShopPageComponent],
  imports: [
    CommonModule,
    SHAREDModule,
   RouterModule.forChild(routes)
  ],
  exports:[RouterModule]
  })
export class ShopModule { }

应用路由模块(AppRoutingModule)

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomePageComponent } from './home/home-page/home-page.component';


const routes: Routes = [
{path:'',component:HomePageComponent},
{path:"Shop", loadChildren:()=>import('./Shop/shop.module').then(m=>m.ShopModule)},
{path:"Account", loadChildren:()=>import('./authentication/authentication.module').then(m=>m.AuthenticationModule)}
//{path:'**',redirectTo:"/",pathMatch:'full'}

];

@NgModule({
  imports: [RouterModule.forRoot(routes) ],
  exports: [RouterModule]
})
export class AppRoutingModule {

 }

应用模块(AppModule)

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import {HttpClientModule} from '@angular/common/http'
import { AppRoutingModule } from './app-routing.module';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

import { AppComponent } from './app.component';

import { OrderModule } from './order/order.module';
import { HomeModule } from './home/home.module';

import { ShoppingCartModule } from './ShoppingCart/shopping-cart.module';

import { COREModule } from './core/core.module';
import { SHAREDModule } from './shared/shared.module';
import { ShopModule } from './Shop/shop.module';

@NgModule({
  declarations: [
    AppComponent,
 
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    HttpClientModule,
    AppRoutingModule,

    OrderModule,
    HomeModule,
    ShoppingCartModule,
    
    COREModule,
    SHAREDModule,
  ],
  providers: [ ],
  bootstrap: [AppComponent]
})
export class AppModule { }

应用根组件(AppComponent)

import { Component, OnInit } from '@angular/core';
import { AccountService } from './core/Services/account.service';
import { ShoppingCartService } from './core/Services/ShoppingCartService';


@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
  
cardId : string;
token : string;
title:string = 'Coffee Corner';


  ngOnInit(): void {
   this.getCurrentCart();   
   this.getCurrentUser(); 
   
  }
 
constructor(private _shoppingCartService:ShoppingCartService, private _accountService:AccountService) {
}

getCurrentCart(){
  this.cardId = localStorage.getItem("shoppingCartId")

  if(this.cardId!=null){
       this._shoppingCartService.getShoppingCart(this.cardId);
     }
}

getCurrentUser(){
  this.token = localStorage.getItem("token");
  this._accountService.loadCurrentUser(this.token).subscribe(); 
}


}

问题根源

你的ShoppingCartService没有被设置为全局单例。Angular中,懒加载模块会创建独立的注入器,如果懒加载模块的依赖链中能找到ShoppingCartService的提供方,就会生成新的服务实例,导致和AppComponent中使用的不是同一个实例,自然无法共享BehaviorSubject保存的购物车状态。

具体来看:

  • ShoppingCartService的@Injectable()装饰器没有指定providedIn: 'root',说明服务的提供方是在某个模块中声明的。
  • 你在AppModule中导入了COREModule,假设ShoppingCartService是在COREModule中提供的,但如果COREModule没有设计成单例模式,或者ShopModule也导入了COREModule,就会创建多个服务实例。
  • 另外,如果ShopModule导入的SHAREDModule也提供了ShoppingCartService,同样会触发新实例的创建。

修复方案

1. 将服务设置为全局单例(推荐)

直接修改ShoppingCartService的@Injectable装饰器,指定providedIn: 'root',Angular会自动将其注册为全局单例,所有模块(无论预加载还是懒加载)都会共享同一个实例:

@Injectable({
  providedIn: 'root'
})
export class ShoppingCartService {
  // ... 原有代码保持不变
}

2. 确保COREModule是单例模块(若坚持用模块提供服务)

如果需要通过模块来提供服务,需将COREModule设计为单例模块,使用forRoot()模式避免重复导入:

  • 修改COREModule:
import { NgModule, Optional, SkipSelf } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ShoppingCartService } from './Services/ShoppingCartService';

@NgModule({
  imports: [CommonModule],
  declarations: []
})
export class COREModule {
  // 防止重复导入
  constructor(@Optional() @SkipSelf() parentModule: COREModule) {
    if (parentModule) {
      throw new Error('COREModule已加载,请仅在AppModule中导入一次');
    }
  }

  // 提供服务的单例入口
  static forRoot(): ModuleWithProviders<COREModule> {
    return {
      ngModule: COREModule,
      providers: [ShoppingCartService]
    };
  }
}
  • 在AppModule中导入COREModule.forRoot(),而非直接导入COREModule:
// AppModule的imports数组中替换原有COREModule
COREModule.forRoot(),
  • 确保所有懒加载模块(如ShopModule)不要导入COREModule。

3. 检查懒加载模块的依赖链

确认ShopModule及其导入的模块(如SHAREDModule)没有重复提供ShoppingCartService,避免触发新实例的创建。


内容的提问来源于stack exchange,提问作者Omar Afify

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 12:47:40