简体   繁体   English

使用Angular中的RxJ,我是否只需要完成在组件中创建的Observable?

[英]With RxJs in Angular do I need to only complete the Observables created in the component?

Basically I'm still not really sure about the cleanup of reactive code in Angular components. 基本上,我还是不太确定Angular组件中的反应式代码是否清理。 Are the following rules correct and sufficient? 以下规则正确且充分吗?

My rules for a reactive component: 我的反应式组件规则:

  • on manual x.subscribe() : 在手册x.subscribe()上
    • if using x.takeUntil(...) or similar construct then no manual cleanup 如果使用x.takeUntil(...)或类似的构造,则无需手动清理
    • if x.complete() is called in the component scope then no manual cleanup 如果在组件范围内调用了x.complete() ,则无需进行手动清理
    • else manual .unsubscribe() on demand or in ngOnDestroy() 其他手动.unsubscribe()需求或ngOnDestroy()
  • on template's observable | 在模板的可观察| async : 异步
    • no manual cleanup needed plus onPush compatible 无需手动清理,并且兼容onPush
  • on manual direct creation of Observable like new Subject() : 关于手动直接创建Observable,如new Subject()
    • manual .complete() in ngOnDestroy() else never closes ngOnDestroy ()中的manual .complete()否则永远不会关闭
  • on manual creation using other Observables like Observable.merge(...) : 在使用其他Observables(如Observable.merge(...))进行手动创建时:
    • no manual cleanup needed as long as subscription is cleaned up 只要清理订阅,就无需手动清理

I'm not really sure about the last bullet point. 我不确定最后一点。

Here is a full example of a real component I refactored according to those rules, can someone see whether there is danger for a memory leak? 这是我根据这些规则重构的真实组件的完整示例,有人可以看到内存泄漏是否存在危险吗? All public $ observables are subscribed via async pipe in the template so I'm sure that the subscriptions are taken care of. 所有公共的$ observable都是通过模板中的异步管道进行订阅的,因此我确定订阅已得到处理。 The subjects created with new are manually cleaned up so that should be OK. 使用new创建的主题将被手动清理,因此应该可以。 The parts I'm worried about are the Observable.combineLatest(...) observables. 我担心的部分是Observable.combineLatest(...)可观察对象。

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);

}

You need to unsubscribe each and every of your subscriptions, for example in the ngOnDestroy() method. 您需要取消订阅每个订阅,例如在ngOnDestroy()方法中。

Or you may 'limit' the observables using operators such as takeUntil() , take() , first() etc. 或者您可以使用诸如takeUntil()take()first()等运算符来“限制”可观察对象。

The above rules are valid for combineLatest as well. 上面的规则也适用于combineLatest

Do not worry about subscriptions created by AsyncPipe because AsyncPipe itself is responsible for unsubscribting for its own subscriptions. 不必担心AsyncPipe创建的订阅,因为AsyncPipe本身负责取消订阅其自己的订阅。

A good read on this topic is the article by Ben Lesh who is the RxJS lead. RxJS负责人Ben Lesh的文章对此主题有很好的阅读。

OK I actually just tested it with .finally(...) plus logging for all intermediate Observables and it gets fired properly when the component is destroyed. 好的,我实际上只是使用.finally(...)加上所有中间Observable的日志记录对其进行了测试,并且在销毁组件时可以正确触发它。

Thus I declare the rules above correct und sufficient. 因此,我宣布上述规则正确无误。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM