RxJS:takeUntil()角度组件的ngOnDestroy()

时间:2017-02-27 16:01:18

标签: angular components rxjs rxjs5

tl; dr:基本上我想将Angular的ngOnDestroy与Rxjs takeUntil()运算符结合起来。 - 那可能吗?

我有一个Angular组件,可以打开几个Rxjs订阅。 当组件被销毁时,需要关闭它们。

一个简单的解决方案是:

class myComponent {

  private subscriptionA;
  private subscriptionB;
  private subscriptionC;

  constructor(
    private serviceA: ServiceA,
    private serviceB: ServiceB,
    private serviceC: ServiceC) {}

  ngOnInit() {
    this.subscriptionA = this.serviceA.subscribe(...);
    this.subscriptionB = this.serviceB.subscribe(...);
    this.subscriptionC = this.serviceC.subscribe(...);
  }

  ngOnDestroy() {
    this.subscriptionA.unsubscribe();
    this.subscriptionB.unsubscribe();
    this.subscriptionC.unsubscribe();
  }

}

这有效,但有点多余。我特别不喜欢那样 - unsubscribe()在其他地方,所以你必须记住这些是相互联系的。 - 组件状态受订阅污染。

我更喜欢使用takeUntil()运算符或类似的东西,使它看起来像这样:

class myComponent {

  constructor(
    private serviceA: ServiceA,
    private serviceB: ServiceB,
    private serviceC: ServiceC) {}

  ngOnInit() {
    const destroy = Observable.fromEvent(???).first();
    this.subscriptionA = this.serviceA.subscribe(...).takeUntil(destroy);
    this.subscriptionB = this.serviceB.subscribe(...).takeUntil(destroy);
    this.subscriptionC = this.serviceC.subscribe(...).takeUntil(destroy);
  }

}

是否存在可以使用takeUntil()或其他方式简化组件架构的销毁事件或类似事件? 我意识到我可以在构造函数中自己创建一个事件,或者在ngOnDestroy()内触发但是最终不会使事情变得更简单。

5 个答案:

答案 0 :(得分:41)

你可以利用ReplaySubject

编辑:自RxJS 6.x起不同: 请注意使用pipe()方法。

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

  constructor(
    private serviceA: ServiceA,
    private serviceB: ServiceB,
    private serviceC: ServiceC) {}

  ngOnInit() {
    this.serviceA
      .pipe(takeUntil(this.destroyed$))
      .subscribe(...);
    this.serviceB
      .pipe(takeUntil(this.destroyed$))
      .subscribe(...);
    this.serviceC
      .pipe(takeUntil(this.destroyed$))
      .subscribe(...);
  }

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

这仅适用于RxJS 5.x及更早版本:

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

  constructor(private serviceA: ServiceA) {}

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

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

答案 1 :(得分:10)

嗯,这取决于您关闭订阅的意思。基本上有两种方法可以做到这一点:

  1. 使用完成链的运算符(例如takeWhile())。
  2. 取消订阅来源Observable。
  3. 很高兴知道这两者不一样。

    使用例如takeWhile()时,您会让操作员发送complete通知,该通知会传播给您的观察者。所以,如果你定义:

    ...
    .subscribe(..., ..., () => doWhatever());
    

    然后当你用例如完成链时。 takeWhile()将调用doWhatever()函数。

    例如,它可能如下所示:

    const Observable = Rx.Observable;
    const Subject = Rx.Subject;
    
    let source = Observable.timer(0, 1000);
    let subject = new Subject();
    
    source.takeUntil(subject).subscribe(null, null, () => console.log('complete 1'));
    source.takeUntil(subject).subscribe(null, null, () => console.log('complete 2'));
    source.takeUntil(subject).subscribe(null, null, () => console.log('complete 3'));
    
    setTimeout(() => {
      subject.next();
    }, 3000);
    

    3秒后,将调用所有完整的回调。

    另一方面,当您取消订阅时,您说您不再对源Observable生成的项目感兴趣。然而,这并不意味着源必须完成。你根本不在乎。

    这意味着您可以从Subscription来电中收集所有.subscribe(...)次,并立即取消订阅所有这些内容:

    let subscriptions = new Rx.Subscription();
    let source = Observable.timer(0, 1000);
    
    subscriptions.add(source.subscribe(null, null, () => console.log('complete 1')));
    subscriptions.add(source.subscribe(null, null, () => console.log('complete 2')));
    subscriptions.add(source.subscribe(null, null, () => console.log('complete 3')));
    
    setTimeout(() => {
      subscriptions.unsubscribe();
    }, 3000);
    

    现在3s延迟后,没有任何内容会打印到控制台,因为我们取消订阅并且没有调用完整的回调。

    所以你想要使用的取决于你和你的用例。请注意,取消订阅与完成不同,即使我猜你的情况并不重要。

答案 2 :(得分:9)

使用npm包ng2-rx-componentdestroyed中的componentDestroyed()函数是使用takeUntil的最简单方法:

@Component({
  selector: 'foo',
  templateUrl: './foo.component.html'
})
export class FooComponent implements OnInit, OnDestroy {
  ngOnInit() {
    Observable.interval(1000)
      .takeUntil(componentDestroyed(this)) // <--- magic is here!
      .subscribe(console.log);
  }

  ngOnDestroy() {}
}

这是componentDestroyed()的一个版本,可直接包含在您的代码中:

// Based on https://www.npmjs.com/package/ng2-rx-componentdestroyed
import { OnDestroy } from '@angular/core';
import { ReplaySubject } from 'rxjs/ReplaySubject';

export function componentDestroyed(component: OnDestroy) {
  const oldNgOnDestroy = component.ngOnDestroy;
  const destroyed$ = new ReplaySubject<void>(1);
  component.ngOnDestroy = () => {
    oldNgOnDestroy.apply(component);
    destroyed$.next(undefined);
    destroyed$.complete();
  };
  return destroyed$;
}

答案 3 :(得分:3)

创建基类

import { Subject } from 'rxjs/Rx';
import { OnDestroy } from '@angular/core';

 export abstract class Base implements OnDestroy {

 protected componentDestroyed$: Subject<any>;

constructor() {
    this.componentDestroyed$ = new Subject<void>();

    const destroyFunc = this.ngOnDestroy;
    this.ngOnDestroy = () => {
        destroyFunc.bind(this)();
        this.componentDestroyed$.next();
        this.componentDestroyed$.complete();
    };
}
// placeholder of ngOnDestroy. no need to do super() call of extended class.
public ngOnDestroy() {
    // no-op
}

}

该组件将是

扩展了基类

export class Test extends Base{
}

订阅时

service.takeUntil(this.componentDestroyed$
    .subscribe(...)

这是全局级别的更改,每当您要订阅时,在整个项目中都使用相同的方法。在需要进行的任何更改中,您都可以在基类中进行修改

答案 4 :(得分:1)

如果您的组件与路线直接相关,则可以通过将Router eventstakeUntil()一起使用来避免添加状态。这样,只要您离开组件,它就会自动清理其订阅。

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { MyService } from './my.service';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/takeUntil';

@Component({
    ...
})
export class ExampleComopnent implements OnInit {

    constructor(private router: Router, private myService: MyService) {
    }

    ngOnInit() {
        this.myService.methodA()
            .takeUntil(this.router.events)
            .subscribe(dataA => {
                ...
            });

        this.myService.methodB()
            .takeUntil(this.router.events)
            .subscribe(dataB => {
                ...
            });
    }
}

注意:此简单示例未考虑受保护的路线或已取消的路线导航。如果有可能触发one of the router events但路线导航被取消,您需要对路由器事件进行过滤,以便在适当的时间点触发 - 例如,在Route Guard检查后或导航完成后。

this.myService.methodA()
    .takeUntil(this.router.events.filter(e => e instanceOf NavigationEnd))
    .subscribe(dataA => {
        ...
    });