С RxJs в Angular мне нужно только завершить Observables, созданные в компоненте? - PullRequest
0 голосов
/ 03 мая 2018

По сути, я до сих пор не совсем уверен насчет очистки реактивного кода в Angular-компонентах. Являются ли следующие правила правильными и достаточными?

Мои правила для реактивного компонента:

  • по инструкции x.subscribe () :
    • если используется x.takeUntil (...) или подобная конструкция, то нет ручной очистки
    • если x.complete () вызывается в области действия компонента, очистка вручную не производится
    • остальное руководство .unsubscribe () по запросу или в ngOnDestroy ()
  • на шаблоне наблюдаемый | асинхронные :
    • ручная очистка не требуется плюс onPush-совместимая
  • при ручном прямом создании Observable, например new Subject () :
    • manual .complete () in ngOnDestroy () иначе никогда не закрывается
  • при создании вручную с использованием других Observables, таких как Observable.merge (...) :
    • очистка вручную не требуется, пока очищена подписка

Я не совсем уверен насчет последней точки пули.

Вот полный пример реального компонента, который я реорганизовал в соответствии с этими правилами, может кто-нибудь увидеть, существует ли опасность утечки памяти? Все общедоступные $ observables подписываются через канал async в шаблоне, поэтому я уверен, что с подписками позаботятся. Предметы, созданные с помощью new , очищаются вручную, поэтому все должно быть в порядке. Части, о которых я беспокоюсь, это Observable.combineLatest (...) observables.

import { ChangeDetectionStrategy, Component, Input, OnDestroy } from '@angular/core';
import { Router } from '@angular/router';
import { DeliveryPointId } from 'app/bso';
import { BookmarkService } from 'app/general';
import { EAccess, EPermission, ReduxGetters, ReduxService } from 'app/redux';
import * as routing from 'app/routing';
import { BehaviorSubject, Observable } from 'app/rx';

@Component({
  selector: 'app-last-opened-workitems-widget',
  templateUrl: './last-opened-workitems-widget.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LastOpenedWorkitemsWidgetComponent implements OnDestroy {

  private readonly showPartners$ = new BehaviorSubject(true);
  private readonly showServices$ = new BehaviorSubject(true);
  private readonly showTens$ = new BehaviorSubject(true);
  private readonly showWorklist$ = new BehaviorSubject(true);

  @Input() set showItems(val: boolean) { this.showWorklist$.next(!!val); }
  @Input() set showPartners(val: boolean) { this.showPartners$.next(!!val); }
  @Input() set showProcesses(val: boolean) { this.showServices$.next(!!val); }
  @Input() set showTens(val: boolean) { this.showTens$.next(!!val); }

  constructor(
    private readonly redux: ReduxService,
    private readonly bookmarks: BookmarkService,
    private readonly router: Router,
  ) { }

  canPartners$ = this.redux.watch(ReduxGetters.userAccess).map(access => access[EPermission.Partner] >= EAccess.Read);
  canServices$ = this.redux.watch(ReduxGetters.userAccess).map(access => access[EPermission.Services] >= EAccess.Read);
  canTens$ = this.redux.watch(ReduxGetters.userAccess).map(access => access[EPermission.Tens] >= EAccess.Read);
  canWorklist$ = this.redux.watch(ReduxGetters.userAccess).map(access => access[EPermission.Worklist] >= EAccess.Read);

  lastItems$ = this.bookmarks.onLastOpenedWorkitems.map(ii => [...ii].reverse());
  lastPartners$ = this.bookmarks.onLastOpenedPartners.map(ii => [...ii].reverse());
  lastProcesses$ = this.bookmarks.onLastOpenedProcesses.map(ii => [...ii].reverse());
  lastTens$ = this.bookmarks.onLastOpenedTens.map(ii => [...ii].reverse());

  hasContentPartners$ = Observable
    .combineLatest(
      this.showPartners$.distinctUntilChanged(),
      this.canPartners$,
      this.lastPartners$.map(ii => ii.length > 0))
    .map(oks => oks.every(ii => ii));

  hasContentServices$ = Observable
    .combineLatest(
      this.showServices$.distinctUntilChanged(),
      this.canServices$,
      this.lastProcesses$.map(ii => ii.length > 0))
    .map(oks => oks.every(ii => ii));

  hasContentTens$ = Observable
    .combineLatest(
      this.showTens$.distinctUntilChanged(),
      this.canTens$,
      this.lastTens$.map(ii => ii.length > 0))
    .map(oks => oks.every(ii => ii));

  hasContentWorklist$ = Observable
    .combineLatest(
      this.showWorklist$.distinctUntilChanged(),
      this.canWorklist$,
      this.lastItems$.map(ii => ii.length > 0))
    .map(oks => oks.every(ii => ii));

  hasContent$ = Observable
    .combineLatest(this.hasContentPartners$, this.hasContentServices$, this.hasContentTens$, this.hasContentWorklist$)
    .map(oks => oks.some(ii => ii));

  ngOnDestroy() {
    [this.showPartners$, this.showServices$, this.showTens$, this.showWorklist$].forEach(ii => ii.complete());
  }

  gotoPartner = (id: string) => routing.gotoPartnerItem(this.router, id);
  gotoProcess = (id: number) => routing.gotoProcess(this.router, id);
  gotoTensItem = (id: DeliveryPointId) => routing.gotoTensItem(this.router, id);
  gotoWorkitem = (id: number) => routing.gotoWorkitem(this.router, id);

}

Ответы [ 2 ]

0 голосов
/ 04 мая 2018

ОК. На самом деле я только что протестировал его с .finally (...) плюс ведение журнала для всех промежуточных наблюдаемых, и он запускается правильно, когда компонент уничтожается.

Таким образом, я объявляю приведенные выше правила правильными и достаточными.

0 голосов
/ 03 мая 2018

Вам необходимо отписаться от каждой подписки, например, методом ngOnDestroy().

Или вы можете «ограничить» наблюдаемые, используя такие операторы, как takeUntil(), take(), first() и т. Д.

Приведенные выше правила действительны и для combineLatest.

Не беспокойтесь о подписках, созданных AsyncPipe, потому что AsyncPipe сама ответственна за отказ от подписки на свои собственные подписки.

Хорошая статья на эту тему - статья Бена Леша, лидера RxJS.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...