当我重新访问页面时,订阅触发两次

时间:2019-04-09 13:12:55

标签: angular rxjs

我遇到一个场景,订阅将触发2倍。但是,如果我通过将poller方法复制粘贴到组件中来摆脱服务,那么问题就消失了。

我正在运行Ionic 3,但我认为这并不重要。 “ rxjs”:“ 5.4.3”

unsubscribe()上调用

ngDestroy()

  

my-service.service.ts

public pollingDataReceived = new Subject<any>();

pollForData = (id: string) : Subscription => {
    let stopPollingForStatus = false;
    return Observable.interval(environment['pollingInterval'])
          .takeWhile(() => !stopPollingForStatus)
          .concatMap(() => {
            return this.getAnalysisById(id);
          })
          .subscribe((analysisData) => {
            if (analysisData) {
              if (analysisData.status === 'questions') {
                stopPollingForStatus = false;
              }
              else {
                stopPollingForStatus = true;
                const result = {
                  someData: 'whatever'
                };
                console.log('raise pollingDataReceived event');// This happens ONCE
                this.pollingDataReceived.next(result);
              }
            }
            else {
              alert('no analysis data!');
            }
          });
  }
  

my.component.ts (订阅者)


  private pollingData: Subscription;

  someEventHandler() {
    this.pollingData = this.pollForData(this.id);
  }

  ngOnInit(): void {
    this.myService.pollingDataReceived.subscribe((data) => {
        this.analysis = data.analysisData;
        //This is getting called 2x.  Maybe I double subscribed?
        myNavigator.navigateToPageByStatus(myPage);
    }, (err) => { console.log(err); });
    }

  ngOnDestroy() {
    console.log('ngOnDestroy: unsubscribe pollingData in flow');// This does get called
    this.pollingData.unsubscribe();
  }

6 个答案:

答案 0 :(得分:2)

除非在组件级别提供,否则Angular Services的行为类似于Singleton。因此,pollingDataReceived主题将继续保留其订阅,直到该服务生效为止。

对于您而言,先前访问this.myService.pollingDataReceived的陈旧订阅也将被解雇。因此,订阅者在 revisit 上触发两次。

ngDestroy上进行类似于pollingData的清理将解决此问题。

答案 1 :(得分:1)

处理程序触发两次的原因-是当您多次推送至该主题时,您永远不会退订服务pollingDataReceived主题。您的服务可能只创建一次,而您的组件在重新访问页面时被重新创建了多次。组件被销毁,尽管它们对同一主题的订阅保持不变。

最简单的解决方法是在组件ngOnDestroy方法中以@ashish.gd suggested的形式退订该主题。

每次创建新主题(as suggested here)可能会导致内存泄漏。由于您的旧订阅将继续收听旧主题。提防。

虽然最简单的方法是退订pollingDataReceived主题,但更好的方法是让服务pollForData()返回一个Observable。

除非您需要多个使用者阅读相同 pollingDataReceived主题,否则您无需订阅。以下是反映该想法的示例代码:

pollForData = (id: string) : Observable => {
  return Observable.interval(environment['pollingInterval'])
    .concatMap(() => this.getAnalysisById(id))
    .takeWhile(data => data.status === 'questions')
    // other handling logic goes here
    // .map(), .switchMap(), etc
    // NOTE: no subscription here
}

在控制器中您可以执行类似的操作

// just an utility subject to track component destruction
onDestroy$ = new Subject();

someEventHandler() {
  this.pollForData(this.id).pipe(
    // this will ensure that subscription will be completed on component destruction
    takeUntil(this.onDestroy$)
  ).subscribe((data) => {
      this.analysis = data.analysisData;
      myNavigator.navigateToPageByStatus(myPage);
  }, (err) => { console.log(err); });
}

ngOnDestroy() {
  this.onDestroy$.next(void 0);
}

-

注意1 :目前看来您的服务是在模块级别提供的,这意味着如果您有两个使用者同时请求pollForData(id)(例如,两个组件一页)-这两个请求都将“写入” 同一主题。容易出错的行为。

注意2 :当前在您的代码takeWhile中的谓词将不会运行,直到您在流中收到 new 事件

Observable.interval(environment['pollingInterval'])
  .takeWhile(() => !stopPollingForStatus)

这意味着environment['pollingInterval']时间内,您在内存中将有不必要的订阅。这并不重要,但根据时间安排可能会很麻烦。

-

希望这对您有所帮助,编码愉快!

答案 2 :(得分:1)

使用完成主题,直到

  private pollingData: Subscription;

  private finalise = new Subject<void>();

  someEventHandler() {
    this.pollingData = this.pollForData(this.id);
  }

  ngOnInit(): void {
    this.myService.pollingDataReceived.pipe(
      takeUntil(finalise)
    ).subscribe((data) => {
        this.analysis = data.analysisData;
        //This is getting called 2x.  Maybe I double subscribed?
        myNavigator.navigateToPageByStatus(myPage);
    }, (err) => { console.log(err); });
    }

  ngOnDestroy() {
    this.finalise.next();
    this.finalise.complete();
  }

答案 3 :(得分:0)

我遇到了类似的问题,请尝试以下代码,这只是解决潜在问题的一种解决方法。

this.pollingDataReceived.next(result);
this.pollingDataReceived = new BehaviorSubject<any>(); // Here, create new object with null value

答案 4 :(得分:0)

还有另一种方法可以实现这一目标。您可以使用ReplaySubject中的rxjs

class pollingComponent {
  private destroyed$: ReplaySubject<boolean> = new ReplaySubject(1);

  constructor(
    private pollingService: Service) {}

  ngOnInit() {
    this.pollingService
      .takeUntil(this.destroyed$)
      .subscribe(...);
  }

  ngOnDestroy() {
    this.destroyed$.next(true);
    this.destroyed$.complete();
  }
}

基本上,建议避免使用香草Subject。您可以详细了解差异here

答案 5 :(得分:0)

如果您需要在访问其他组件时保持订阅事件(例如消息传递),您可以通过设置条件来检查事件是否没有观察者,然后才订阅它,从而仅订阅一次。 我相信这不是最佳实践,但确实可以。

它看起来像这样:

ngOnInit(): void {
      if (this.myService.pollingDataReceived.observers.length === 0) {
      this.myService.pollingDataReceived.subscribe((data) => {
          this.analysis = data.analysisData;
          myNavigator.navigateToPageByStatus(myPage);
      },  (err) => { console.log(err); });
    }
  }