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

为什么Angular中BehaviorSubject订阅后会多次触发回调?

问题背景

现有3个组件:

  • InvoicelistComponent
  • CreateinvoiceComponent
  • HeaderInvoiceComponent

InvoicelistComponent 跳转逻辑

提供跳转到CreateInvoice页面的方法,代码如下:

getInvoiceDetail(invoiceId: number) {
  this.router.navigate('mainmenu/invoice/createinvoice', invoiceId)
}

CreateinvoiceComponent 数据拉取逻辑

在ngOnInit中获取invoiceId,调用API拉取数据后存入服务,代码如下:

ngOnInit() {
  let checkMode = this.routerService.data === undefined || null ? false : true;
  if (checkMode === true) {
    this.spinner.show();
    let invoiceId = this.routerService.data;
    this.createInvoiceService.getdatainvoicebyid(invoiceId).subscribe(res => {
      let invoicemodel = new Invoice();
      invoicemodel = res;
      this.searchService.setdatainvoiceData(invoicemodel['result'][0]);
      this.spinner.hide();
    });
  }
}

SearchService 跨组件传值逻辑

使用BehaviorSubject传递数据,相关代码如下:

public setInvoiceData = new BehaviorSubject<any>(null);
setdatainvoiceData(data: any) {
  this.setInvoiceData.next(data);
}

HeaderInvoiceComponent 数据订阅逻辑

在构造函数中订阅该BehaviorSubject获取数据,代码如下:

this.searchService.setInvoiceData.subscribe(res => {
  if (res != null || res != undefined) {
    console.log(res);
    this.objRequestParam.InvoiceType = res.Invoiceheader.invoicetypeid
    this.objRequestParam.InvoiceSubType = res.Invoiceheader.invoicesubtypeid
    this.objRequestParam.InvoiceDate = res.Invoiceheader.invoicedate;
    this.objRequestParam.txtInvoiceNo =  res.Invoiceheader.invoiceno;
    this.radioValue = res.Invoiceheader.invoicereferencetypeid;
  }
}); 
现有问题
  1. 第一次点击绿色图标跳转时,HeaderInvoiceComponent的订阅触发2次,第1次返回null、第2次返回有效数据,添加非空判断后仅显示1次有效触发,请问如何优化为仅触发1次有效回调?
  2. 返回上一页后点击另一条目的绿色图标跳转,订阅会触发3次,请问是什么原因?
解决方案

问题1原因及优化方案

核心原因:BehaviorSubject的特性是创建时必须指定初始值,新订阅者接入时会优先推送当前存储的最新值,也就是你设置的初始值null,后续接口请求完成调用next推送新数据时才会触发第二次回调,所以才会出现两次触发的情况。

可选优化方案:

  1. 替换Subject类型:把BehaviorSubject换成ReplaySubject(1),ReplaySubject不需要初始值,只会推送调用next之后的历史数据,从根源上避免无效的初始值触发,修改SearchService代码即可:
// 替换原有BehaviorSubject声明
public setInvoiceData = new ReplaySubject<any>(1);
  1. 过滤无效回调:如果需要保留BehaviorSubject,就在订阅时添加RxJS的skip(1)操作符,跳过第一次初始值推送即可:
// HeaderInvoiceComponent订阅逻辑修改
this.searchService.setInvoiceData
  .pipe(skip(1))
  .subscribe(res => {
    // 原有业务逻辑不变
  })

另外注意你现有代码的非空判断逻辑存在错误:res != null || res != undefined是或条件,无论res是null还是undefined都会满足条件进入逻辑,应该改成res != null && res != undefined,或者直接简化为if(res)即可。

问题2原因及修复方案

核心原因:你没有在HeaderInvoiceComponent销毁时手动取消订阅,每次进入页面创建新的组件实例时都会新增一个订阅,旧实例的订阅不会跟随组件销毁自动清除,所以跳转次数越多,累计的订阅数越多,触发回调的次数也会对应增加。

修复方案:在组件中存储订阅引用,组件销毁时手动取消订阅即可,修改HeaderInvoiceComponent代码:

// 新增变量存储订阅实例
private invoiceSub: Subscription;

constructor(private searchService: SearchService) {
  // 订阅时赋值给变量
  this.invoiceSub = this.searchService.setInvoiceData.subscribe(res => {
    // 原有业务逻辑不变
  })
}

// 新增销毁生命周期钩子取消订阅
ngOnDestroy() {
  if(this.invoiceSub) {
    this.invoiceSub.unsubscribe();
  }
}

如果使用Angular16及以上版本,也可以直接用takeUntilDestroyed操作符简化写法,无需手动处理取消逻辑。


内容的提问来源于stack exchange,提问作者Nok Pollawat

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.17 07:06:05