Angular/RxJs When should I unsubscribe from `Subscription`

前端 未结 22 2255
隐瞒了意图╮
隐瞒了意图╮ 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:34

    The Subscription class has an interesting feature:

    Represents a disposable resource, such as the execution of an Observable. A Subscription has one important method, unsubscribe, that takes no argument and just disposes the resource held by the subscription.
    Additionally, subscriptions may be grouped together through the add() method, which will attach a child Subscription to the current Subscription. When a Subscription is unsubscribed, all its children (and its grandchildren) will be unsubscribed as well.

    You can create an aggregate Subscription object that groups all your subscriptions. You do this by creating an empty Subscription and adding subscriptions to it using its add() method. When your component is destroyed, you only need to unsubscribe the aggregate subscription.

    @Component({ ... })
    export class SmartComponent implements OnInit, OnDestroy {
      private subscriptions = new Subscription();
    
      constructor(private heroService: HeroService) {
      }
    
      ngOnInit() {
        this.subscriptions.add(this.heroService.getHeroes().subscribe(heroes => this.heroes = heroes));
        this.subscriptions.add(/* another subscription */);
        this.subscriptions.add(/* and another subscription */);
        this.subscriptions.add(/* and so on */);
      }
    
      ngOnDestroy() {
        this.subscriptions.unsubscribe();
      }
    }
    
    0 讨论(0)
  • 2020-11-21 05:35

    For handling subscription I use a "Unsubscriber" class.

    Here is the Unsubscriber Class.

    export class Unsubscriber implements OnDestroy {
      private subscriptions: Subscription[] = [];
    
      addSubscription(subscription: Subscription | Subscription[]) {
        if (Array.isArray(subscription)) {
          this.subscriptions.push(...subscription);
        } else {
          this.subscriptions.push(subscription);
        }
      }
    
      unsubscribe() {
        this.subscriptions
          .filter(subscription => subscription)
          .forEach(subscription => {
            subscription.unsubscribe();
          });
      }
    
      ngOnDestroy() {
        this.unsubscribe();
      }
    }
    

    And You can use this class in any component / Service / Effect etc.

    Example:

    class SampleComponent extends Unsubscriber {
        constructor () {
            super();
        }
    
        this.addSubscription(subscription);
    }
    
    0 讨论(0)
  • 2020-11-21 05:37

    Some of the best practices regarding observables unsubscriptions inside Angular components:

    A quote from Routing & Navigation

    When subscribing to an observable in a component, you almost always arrange to unsubscribe when the component is destroyed.

    There are a few exceptional observables where this is not necessary. The ActivatedRoute observables are among the exceptions.

    The ActivatedRoute and its observables are insulated from the Router itself. The Router destroys a routed component when it is no longer needed and the injected ActivatedRoute dies with it.

    Feel free to unsubscribe anyway. It is harmless and never a bad practice.

    And in responding to the following links:

    • (1) Should I unsubscribe from Angular 2 Http Observables?
    • (2) Is it necessary to unsubscribe from observables created by Http methods?
    • (3) RxJS: Don’t Unsubscribe
    • (4) The easiest way to unsubscribe from Observables in Angular
    • (5) Documentation for RxJS Unsubscribing
    • (6) Unsubscribing in a service is kind of pointless since there is no chance of memory leaks
    • (7) Do we need to unsubscribe from observable that completes/errors-out?
    • (8) A comment about the http observable

    I collected some of the best practices regarding observables unsubscriptions inside Angular components to share with you:

    • http observable unsubscription is conditional and we should consider the effects of the 'subscribe callback' being run after the component is destroyed on a case by case basis. We know that angular unsubscribes and cleans the http observable itself (1), (2). While this is true from the perspective of resources it only tells half the story. Let's say we're talking about directly calling http from within a component, and the http response took longer than needed so the user closed the component. The subscribe() handler will still be called even if the component is closed and destroyed. This can have unwanted side effects and in the worse scenarios leave the application state broken. It can also cause exceptions if the code in the callback tries to call something that has just been disposed of. However at the same time occasionally they are desired. Like, let's say you're creating an email client and you trigger a sound when the email is done sending - well you'd still want that to occur even if the component is closed (8).
    • No need to unsubscribe from observables that complete or error. However, there is no harm in doing so(7).
    • Use AsyncPipe as much as possible because it automatically unsubscribes from the observable on component destruction.
    • Unsubscribe from the ActivatedRoute observables like route.params if they are subscribed inside a nested (Added inside tpl with the component selector) or dynamic component as they may be subscribed many times as long as the parent/host component exists. No need to unsubscribe from them in other scenarios as mentioned in the quote above from Routing & Navigation docs.
    • Unsubscribe from global observables shared between components that are exposed through an Angular service for example as they may be subscribed multiple times as long as the component is initialized.
    • No need to unsubscribe from internal observables of an application scoped service since this service never get's destroyed, unless your entire application get's destroyed, there is no real reason to unsubscribe from it and there is no chance of memory leaks. (6).

      Note: Regarding scoped services, i.e component providers, they are destroyed when the component is destroyed. In this case, if we subscribe to any observable inside this provider, we should consider unsubscribing from it using the OnDestroy lifecycle hook which will be called when the service is destroyed, according to the docs.
    • Use an abstract technique to avoid any code mess that may be resulted from unsubscriptions. You can manage your subscriptions with takeUntil (3) or you can use this npm package mentioned at (4) The easiest way to unsubscribe from Observables in Angular.
    • Always unsubscribe from FormGroup observables like form.valueChanges and form.statusChanges
    • Always unsubscribe from observables of Renderer2 service like renderer2.listen
    • Unsubscribe from every observable else as a memory-leak guard step until Angular Docs explicitly tells us which observables are unnecessary to be unsubscribed (Check issue: (5) Documentation for RxJS Unsubscribing (Open)).
    • Bonus: Always use the Angular ways to bind events like HostListener as angular cares well about removing the event listeners if needed and prevents any potential memory leak due to event bindings.

    A nice final tip: If you don't know if an observable is being automatically unsubscribed/completed or not, add a complete callback to subscribe(...) and check if it gets called when the component is destroyed.

    0 讨论(0)
  • 2020-11-21 05:39

    Following the answer by @seangwright, I've written an abstract class that handles "infinite" observables' subscriptions in components:

    import { OnDestroy } from '@angular/core';
    import { Subscription } from 'rxjs/Subscription';
    import { Subject } from 'rxjs/Subject';
    import { Observable } from 'rxjs/Observable';
    import { PartialObserver } from 'rxjs/Observer';
    
    export abstract class InfiniteSubscriberComponent implements OnDestroy {
      private onDestroySource: Subject<any> = new Subject();
    
      constructor() {}
    
      subscribe(observable: Observable<any>): Subscription;
    
      subscribe(
        observable: Observable<any>,
        observer: PartialObserver<any>
      ): Subscription;
    
      subscribe(
        observable: Observable<any>,
        next?: (value: any) => void,
        error?: (error: any) => void,
        complete?: () => void
      ): Subscription;
    
      subscribe(observable: Observable<any>, ...subscribeArgs): Subscription {
        return observable
          .takeUntil(this.onDestroySource)
          .subscribe(...subscribeArgs);
      }
    
      ngOnDestroy() {
        this.onDestroySource.next();
        this.onDestroySource.complete();
      }
    }
    

    To use it, just extend it in your angular component and call the subscribe() method as follows:

    this.subscribe(someObservable, data => doSomething());
    

    It also accepts the error and complete callbacks as usual, an observer object, or not callbacks at all. Remember to call super.ngOnDestroy() if you are also implementing that method in the child component.

    Find here an additional reference by Ben Lesh: RxJS: Don’t Unsubscribe.

    0 讨论(0)
提交回复
热议问题