Angular/RxJs When should I unsubscribe from `Subscription`

前端 未结 22 2197
隐瞒了意图╮
隐瞒了意图╮ 2020-11-21 04:56

When should I store the Subscription instances and invoke unsubscribe() during the NgOnDestroy life cycle and when can I simply ignore them?

22条回答
  •  礼貌的吻别
    2020-11-21 05:25

    The official Edit #3 answer (and variations) works well, but the thing that gets me is the 'muddying' of the business logic around the observable subscription.

    Here's another approach using wrappers.

    Warining: experimental code

    File subscribeAndGuard.ts is used to create a new Observable extension to wrap .subscribe() and within it to wrap ngOnDestroy().
    Usage is the same as .subscribe(), except for an additional first parameter referencing the component.

    import { Observable } from 'rxjs/Observable';
    import { Subscription } from 'rxjs/Subscription';
    
    const subscribeAndGuard = function(component, fnData, fnError = null, fnComplete = null) {
    
      // Define the subscription
      const sub: Subscription = this.subscribe(fnData, fnError, fnComplete);
    
      // Wrap component's onDestroy
      if (!component.ngOnDestroy) {
        throw new Error('To use subscribeAndGuard, the component must implement ngOnDestroy');
      }
      const saved_OnDestroy = component.ngOnDestroy;
      component.ngOnDestroy = () => {
        console.log('subscribeAndGuard.onDestroy');
        sub.unsubscribe();
        // Note: need to put original back in place
        // otherwise 'this' is undefined in component.ngOnDestroy
        component.ngOnDestroy = saved_OnDestroy;
        component.ngOnDestroy();
    
      };
    
      return sub;
    };
    
    // Create an Observable extension
    Observable.prototype.subscribeAndGuard = subscribeAndGuard;
    
    // Ref: https://www.typescriptlang.org/docs/handbook/declaration-merging.html
    declare module 'rxjs/Observable' {
      interface Observable {
        subscribeAndGuard: typeof subscribeAndGuard;
      }
    }
    

    Here is a component with two subscriptions, one with the wrapper and one without. The only caveat is it must implement OnDestroy (with empty body if desired), otherwise Angular does not know to call the wrapped version.

    import { Component, OnInit, OnDestroy } from '@angular/core';
    import { Observable } from 'rxjs/Observable';
    import 'rxjs/Rx';
    import './subscribeAndGuard';
    
    @Component({
      selector: 'app-subscribing',
      template: '

    Subscribing component is active

    ', }) export class SubscribingComponent implements OnInit, OnDestroy { ngOnInit() { // This subscription will be terminated after onDestroy Observable.interval(1000) .subscribeAndGuard(this, (data) => { console.log('Guarded:', data); }, (error) => { }, (/*completed*/) => { } ); // This subscription will continue after onDestroy Observable.interval(1000) .subscribe( (data) => { console.log('Unguarded:', data); }, (error) => { }, (/*completed*/) => { } ); } ngOnDestroy() { console.log('SubscribingComponent.OnDestroy'); } }

    A demo plunker is here

    An additional note: Re Edit 3 - The 'Official' Solution, this can be simplified by using takeWhile() instead of takeUntil() before subscriptions, and a simple boolean rather than another Observable in ngOnDestroy.

    @Component({...})
    export class SubscribingComponent implements OnInit, OnDestroy {
    
      iAmAlive = true;
      ngOnInit() {
    
        Observable.interval(1000)
          .takeWhile(() => { return this.iAmAlive; })
          .subscribe((data) => { console.log(data); });
      }
    
      ngOnDestroy() {
        this.iAmAlive = false;
      }
    }
    

提交回复
热议问题