Подписка срабатывает дважды, когда я снова захожу на страницу - PullRequest
3 голосов
/ 09 апреля 2019

Я сталкиваюсь с Scenerio, где подписка срабатывает 2 раза. Однако если я избавлюсь от службы, скопировав метод poller в свой компонент, проблема исчезнет.

Я работаю в Ionic 3, но я не думаю, что это имеет значение здесь. "rxjs": "5.4.3"

unsubscribe() вызывается ngDestroy()

мой-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();
  }

Ответы [ 5 ]

2 голосов
/ 09 апреля 2019

Службы Angular ведут себя как одиночные, если они не предоставляются на уровне компонентов.Следовательно, субъект pollingDataReceived будет продолжать удерживать свои подписки до тех пор, пока услуга не окажется в области действия.

В вашем случае устаревшая подписка на this.myService.pollingDataReceived от предыдущего посещения также будет запущена.Следовательно, подписчик дважды стреляет по revisit .

Очистка его по ngDestroy подобно pollingData устранит проблему.

1 голос
/ 10 апреля 2019

Используйте завершающий предмет и продолжайте до

  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();
  }
1 голос
/ 09 апреля 2019

Причина, по которой обработчик запускается дважды, заключается в том, что вы никогда не отмените подписку на услугу. pollingDataReceived Тема, а вы несколько раз нажимаете на эту тему. Вероятно, ваш сервис был создан один раз, а ваши компоненты были повторно созданы несколько раз с повторным посещением страницы. Компоненты были уничтожены, хотя их подписки на ту же тему оставались нетронутыми. Самый простой способ обойти это отписаться от этой темы в методе компонента ngOnDestroy, как предложил @ashish.gd .

Создание новых объектов каждый раз (, как предлагается здесь ), вероятно, приведет к утечкам памяти. Поскольку ваши старые подписки будут продолжать слушать старые темы. Осторожно.

В то время как самым простым способом было бы отписаться от 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 предикат не будет запускаться, пока вы не получите новое событие в потоке

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

Это означает, что у вас будет ненужная подписка в памяти на environment['pollingInterval'] раз. Это не должно быть критичным, но это может быть неприятно в зависимости от времени.

-

Надеюсь, это поможет, удачного кодирования!

0 голосов
/ 10 апреля 2019

Есть и другой способ добиться этого. Вы можете использовать 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. Подробнее о разнице можно прочитать здесь

0 голосов
/ 09 апреля 2019

Я столкнулся с подобной проблемой, попробуйте код ниже, который является просто обходной путь для основной проблемы.

this.pollingDataReceived.next(result);
this.pollingDataReceived = new BehaviorSubject<any>(); // Here, create new object with null value
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...