1020

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

Saving all subscriptions introduces a lot of mess into component code.

HTTP Client Guide ignore subscriptions like this:

getHeroes() {
  this.heroService.getHeroes()
                  .subscribe(
                     heroes => this.heroes = heroes,
                     error =>  this.errorMessage = <any>error);
}

In the same time Route & Navigation Guide says that:

Eventually, we'll navigate somewhere else. The router will remove this component from the DOM and destroy it. We need to clean up after ourselves before that happens. Specifically, we must unsubscribe before Angular destroys the component. Failure to do so could create a memory leak.

We unsubscribe from our Observable in the ngOnDestroy method.

private sub: any;

ngOnInit() {
  this.sub = this.route.params.subscribe(params => {
     let id = +params['id']; // (+) converts string 'id' to a number
     this.service.getHero(id).then(hero => this.hero = hero);
   });
}

ngOnDestroy() {
  this.sub.unsubscribe();
}
Audwin Oyong
  • 2,247
  • 3
  • 15
  • 32
Sergey Tihon
  • 12,071
  • 4
  • 23
  • 29
  • 32
    I guess `Subscription`s to `http-requests` can be ignored, as they only call `onNext` once and then they call `onComplete`. The `Router` instead calls `onNext` repeatedly and might never call `onComplete` (not sure about that...). Same goes for `Observable`s from `Event`s. So I guess those should be `unsubscribed`. – Robert P Jun 24 '16 at 08:41
  • 1
    @gt6707a The stream completes (or does not complete) independent of any observation of that completion. The callbacks (the observer) provided to the subscription function do not determine if resources are allocated. It is the call to `subscribe` itself that potentially allocates resources upstream. – seangwright Dec 16 '16 at 03:31
  • Make it a `muscle memory` to unsubscribe explicitly in your `typescript`. Even the `http` subscriptions. Ex: A `Http.get()` completes on the response. If your server api takes `10 seconds` to respond, and your component is destroyed within `5 seconds` of the call, your response will arrive `5 seconds` `after` the component destruction. That will trigger an out-of-context execution, which is far worse than the memory leak portion of it, indicated in Angular docs. – Val Neekman Oct 26 '21 at 18:21
  • 1
    @unk33k mind sharing the exact link to the documentation? Sorry, can't seem to find that bit. – Tyiliyra Feb 02 '22 at 07:53

29 Answers29

1304

TL;DR

For this question there are two kinds of Observables - finite value and infinite value.

http Observables produce finite (1) values and something like a DOM event listener Observable produces infinite values.

If you manually call subscribe (not using async pipe), then unsubscribe from infinite Observables.

Don't worry about finite ones, RxJs will take care of them.


Sources:

  1. I tracked down an answer from Rob Wormald in Angular's Gitter here.

    He states (I reorganized for clarity and emphasis is mine):

    if its a single-value-sequence (like an http request) the manual cleanup is unnecessary (assuming you subscribe in the controller manually)

    i should say "if its a sequence that completes" (of which single value sequences, a la http, are one)

    if its an infinite sequence, you should unsubscribe which the async pipe does for you

    Also he mentions in this YouTube video on Observables that "they clean up after themselves..." in the context of Observables that complete (like Promises, which always complete because they are always producing one value and ending - we never worried about unsubscribing from Promises to make sure they clean up XHR event listeners, right?)

  2. Also in the Rangle guide to Angular 2 it reads

    In most cases we will not need to explicitly call the unsubscribe method unless we want to cancel early or our Observable has a longer lifespan than our subscription. The default behavior of Observable operators is to dispose of the subscription as soon as .complete() or .error() messages are published. Keep in mind that RxJS was designed to be used in a "fire and forget" fashion most of the time.

    When does the phrase "our Observable has a longer lifespan than our subscription" apply?

    It applies when a subscription is created inside a component which is destroyed before (or not 'long' before) the Observable completes.

    I read this as meaning if we subscribe to an http request or an Observable that emits 10 values and our component is destroyed before that http request returns or the 10 values have been emitted, we are still OK!

    When the request does return or the 10th value is finally emitted the Observable will complete and all resources will be cleaned up.

  3. If we look at this example from the same Rangle guide we can see that the subscription to route.params does require an unsubscribe() because we don't know when those params will stop changing (emitting new values).

    The component could be destroyed by navigating away in which case the route params will likely still be changing (they could technically change until the app ends) and the resources allocated in subscription would still be allocated because there hasn't been a completion.

  4. In this video from NgEurope Rob Wormald also says you do not need to unsubscribe from Router Observables. He also mentions the http service and ActivatedRoute.params in this video from November 2016.

  5. The Angular tutorial, the Routing chapter now states the following:

    The Router manages the observables it provides and localizes the subscriptions. The subscriptions are cleaned up when the component is destroyed, protecting against memory leaks, so we don't need to unsubscribe from the route params Observable.

    Here's a discussion on the GitHub Issues for the Angular docs regarding Router Observables where Ward Bell mentions that clarification for all of this is in the works.


I spoke with Ward Bell about this question at NGConf (I even showed him this answer which he said was correct) but he told me the docs team for Angular had a solution to this question that is unpublished (though they are working on getting it approved). He also told me I could update my SO answer with the forthcoming official recommendation.

The solution we should all use going forward is to add a private ngUnsubscribe = new Subject<void>(); field to all components that have .subscribe() calls to Observables within their class code.

We then call this.ngUnsubscribe.next(); this.ngUnsubscribe.complete(); in our ngOnDestroy() methods.

The secret sauce (as noted already by @metamaker) is to call takeUntil(this.ngUnsubscribe) before each of our .subscribe() calls which will guarantee all subscriptions will be cleaned up when the component is destroyed.

Example:

import { Component, OnDestroy, OnInit } from '@angular/core';
// RxJs 6.x+ import paths
import { filter, startWith, takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { BookService } from '../books.service';

@Component({
    selector: 'app-books',
    templateUrl: './books.component.html'
})
export class BooksComponent implements OnDestroy, OnInit {
    private ngUnsubscribe = new Subject<void>();

    constructor(private booksService: BookService) { }

    ngOnInit() {
        this.booksService.getBooks()
            .pipe(
               startWith([]),
               filter(books => books.length > 0),
               takeUntil(this.ngUnsubscribe)
            )
            .subscribe(books => console.log(books));

        this.booksService.getArchivedBooks()
            .pipe(takeUntil(this.ngUnsubscribe))
            .subscribe(archivedBooks => console.log(archivedBooks));
    }

    ngOnDestroy() {
        this.ngUnsubscribe.next();
        this.ngUnsubscribe.complete();
    }
}

Note: It's important to add the takeUntil operator as the last one to prevent leaks with intermediate Observables in the operator chain.


More recently, in an episode of Adventures in Angular Ben Lesh and Ward Bell discuss the issues around how/when to unsubscribe in a component. The discussion starts at about 1:05:30.

Ward mentions "right now there's an awful takeUntil dance that takes a lot of machinery" and Shai Reznik mentions "Angular handles some of the subscriptions like http and routing".

In response Ben mentions that there are discussions right now to allow Observables to hook into the Angular component lifecycle events and Ward suggests an Observable of lifecycle events that a component could subscribe to as a way of knowing when to complete Observables maintained as component internal state.

That said, we mostly need solutions now so here are some other resources.

  1. A recommendation for the takeUntil() pattern from RxJs core team member Nicholas Jamieson and a TSLint rule to help enforce it: https://ncjamieson.com/avoiding-takeuntil-leaks/

  2. Lightweight npm package that exposes an Observable operator that takes a component instance (this) as a parameter and automatically unsubscribes during ngOnDestroy: https://github.com/NetanelBasal/ngx-take-until-destroy

  3. Another variation of the above with slightly better ergonomics if you are not doing AOT builds (but we should all be doing AOT now): https://github.com/smnbbrv/ngx-rx-collector

  4. Custom directive *ngSubscribe that works like async pipe but creates an embedded view in your template so you can refer to the 'unwrapped' value throughout your template: https://netbasal.com/diy-subscription-handling-directive-in-angular-c8f6e762697f

I mention in a comment to Nicholas' blog that over-use of takeUntil() could be a sign that your component is trying to do too much and that separating your existing components into Feature and Presentational components should be considered. You can then | async the Observable from the Feature component into an Input of the Presentational component, which means no subscriptions are necessary anywhere. Read more about this approach here.

dota2pro
  • 7,220
  • 7
  • 44
  • 79
seangwright
  • 17,245
  • 6
  • 42
  • 54
  • When a route is navigated away from, then the child router for that route is destroyed. I guess this is why it's not necessary to unsubscribe from router events. – Günter Zöchbauer Jan 17 '17 at 20:21
  • What about local subjects, which are created by a component (e.g. for in-component logic/wiring up): Should `complete()` be called on these subjects in `ngOnDestroy`? That would cleanup the subscriptions and each subscription would have the possibility to clean up, what ever it used, in its complete-handler, right? – Lars Jan 29 '17 at 11:25
  • @Lars I believe local subjects get cleaned up automatically since they are created within the scope of the parent component but the Angular team is going to be recommending the approach I have detailed above in "Edit 3". – seangwright Apr 09 '17 at 23:41
  • 25
    Calling `complete()` by itself doesn't appear to clean up the subscriptions. However calling `next()` and then `complete()` does, I believe `takeUntil()` only stops when a value is produced, not when the sequence is ended. – Firefly Apr 11 '17 at 08:53
  • 4
    @seangwright A quick test with a member of type `Subject` inside a component and toggling it with `ngIf` to trigger `ngOnInit` and `ngOnDestroy` shows, that the subject and its subscriptions will never complete or get disposed (hooked up a `finally`-operator to the subscription). I must call `Subject.complete()` in `ngOnDestroy`, so the subscriptions can clean up after themselves. – Lars Apr 11 '17 at 09:17
  • 1
    @Firefly You are correct - added this to my answer above. @Lars Thanks for for doing the test. I thought local `Subject`s might be in the set of observables that Angular will clean up for you, if that's not the case then the above solution should be used. – seangwright Apr 12 '17 at 10:07
  • 5
    Your *--- Edit 3* is very insightful, thanks! I just have a followup question: if using the `takeUnitl` approach, we never have to manually unsubscribe from any observables? Is that the case? Furthermore, why do we need to call `next()` in the `ngOnDestroy`, why not just call `complete()`? – uglycode Apr 22 '17 at 10:21
  • 2
    @uglycode With this approach you never have to unsubscribe unless you want further custom control of your subscriptions. Look at @Firefly's comment above. Calling `complete()` does not trigger `takeUntil()`. But it does clean up the `ngUnsubscribe` Subject. So `next()` cleans up all the others and `complete()` cleans up itself. – seangwright Apr 22 '17 at 17:24
  • 1
    @seangwright The documentation for `takeUntil` says that it listens for the observable to either emit a value or a complete notification, so it seems like just calling `complete` on the subject is enough. – spongessuck Apr 26 '17 at 15:14
  • 2
    @spongessuck The docs do seem to be contradictory, but looking at [the RxJS 5 docs](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/takeuntil.md) `Returns the values from the source observable sequence until the other observable sequence or Promise produces a value.` Looking [at the source](https://github.com/Reactive-Extensions/RxJS/blob/master/src/modular/observable/takeuntil.js#L18) when the takeUntil Observer calls `next` the source completes. @firefly mentions above that calling `complete()` alone doesn't seem to do the trick. – seangwright Apr 26 '17 at 19:54
  • 9
    @seangwright That's disappointing; the additional boilerplate is annoying. – spongessuck Apr 27 '17 at 20:30
  • @spongessuck There is a decorator you can use to handle some of the boilerplate https://github.com/NetanelBasal/angular2-take-until-destroy. I prefer the more explicit approach, but it's a preference. – seangwright Apr 28 '17 at 22:52
  • first nice post,... my question is if you have a source for your newest edit (EDIT 3), if so could you append it to your answer, thx in advance – Nickolaus May 22 '17 at 13:27
  • @Nickolaus Thanks! I've tried to collect as much information here as possible. My source was a conversation with [Ward Bell](https://twitter.com/wardbell?lang=en) at NGConf this year. The official sources have not yet been published in the Angular.io docs (as shown by [this thread](https://github.com/angular/angular.io/issues/3003#issuecomment-268429065)). Unfortunately, this is the best I can do at the moment. You could tweet at ward and ask him about the status on the docs. – seangwright May 22 '17 at 16:41
  • what if I'm subscribing to `valueChanges` of a `FormControl` that is a part of a given component? Do I still need to unsubscribe? – Dmitry Efimenko May 26 '17 at 19:28
  • 1
    @Dmitry It depends. If that subscription is the `valueChanges` Observable combined with other observables that outlive the lifetime of the component, then yes, you should use the above pattern. But if you are only subscribing to the `valueChanges` Observable then it will be destroyed/cleaned up when the component (and the form) are destroyed. – seangwright May 26 '17 at 22:43
  • 4
    **Edit 3** discussed in context of events at https://medium.com/@benlesh/rxjs-dont-unsubscribe-6753ed4fda87 – HankCa May 29 '17 at 02:07
  • If following the above pattern what would I spy on to unit test 'ngOnDestroy'? Does this same pattern apply if I'm using ReplaySubject to create an observable from an array that changes? The array is in a service that's meant to live throughout the app's existence. The subscriber for the most part will also have the same lifetime, but in some instances could be destroyed and later reinitialized. – Jens Bodal Jun 08 '17 at 15:29
  • 1
    @seangwright, thanks for staying on top of this answer! I have a question that I am still not entirely clear on. If I create a new Subject, should we also be unsubscribing from these too? I haven't been able to find any answers or blog posts that has cleared this up for me. – bmd Jun 13 '17 at 13:49
  • 1
    @bmd Take a look at [@Lars's comment](https://stackoverflow.com/questions/38008334/angular-rxjs-when-should-i-unsubscribe-from-subscription/41177163?noredirect=1#comment73746448_41177163) where it is mentioned that local Subjects do not complete when a component is destroyed unless `.complete()` is called on them. This is an example of where having an `ngUnsubscribe: Subject` would help manage all other subscriptions (whether from Observables provided by DOM events, injectable services or other local `Subject`s). – seangwright Jun 13 '17 at 21:31
  • Ok, this is very interesting. So did I understand that correctly: I should NOT call this.subscription.unsubscribe() on EVERY subscription, but just need to implement the .takeUntil() method to every Observable and call a .complete() in the ngOnDestroy() method only once? Can you please confirm? – dave0688 Dec 19 '17 at 16:15
  • @dave0688 that is correct. You can keep track of and call `.unsubscribe()` on every subscription (via `for ... of`), but using `.takeUntil(sub)` or `.pipe(takeUntil(sub))` is the more rxjs-ish approach – seangwright Dec 19 '17 at 21:26
  • @seangwright - I am now trying this solution (edit 3). If I console.log(this.ngUnsubscribe) before the 'this.ngUnsubscribe.next()' I see that the observers property of it is an array with 0 items. does it make sense? – Batsheva Jan 08 '18 at 13:02
  • @seangwright I'm a bit confused as to when to use the lettable operator version of takeUntil. In my component, I have subscriptions to observables returned from a service which makes httpclient requests. In those requests, I use the pipe (tap, catchError, etc). Is that when that version should be used? – Alex Jan 22 '18 at 00:01
  • @seangwright. Well After a bit more research, I learned that HTTP requests are finite observables and therefore don't have to be unsubscribed. However, I'd still like to see from fleshed out examples of the two forms of takeUntil. – Alex Jan 22 '18 at 00:17
  • 3
    @Alex That is correct, HTTP requests, as explained in my answer above, are finite. But that doesn't mean that an observable that starts out as finite will always result in a finite stream. The RxJs operators allow you to manipulate the stream or data so an initial http request could become an infinite stream of numbers or simulated mouse clicks. This is why the above pattern is recommended. No matter what new operators you add to your http observable you are guaranteed it will be cleaned up. – seangwright Jan 22 '18 at 02:43
  • @Alex Lettable operators are a bundle/syntax change, not a functional change. You would use them when using a new enough version of RxJs (^5.5.0) to help ensure you import only what is needed per ES module (ie per file). https://blog.angularindepth.com/rxjs-understanding-lettable-operators-fe74dda186d3 – seangwright Jan 22 '18 at 02:45
  • 1
    Why isn't this "official solution" part of the framework yet? – thorn0 Feb 14 '18 at 15:58
  • 2
    @thorn Good question. It might be that the Angular team wants us to default to letting subscriptions manage themselves using features of the framework (`async pipe`). RxJs allows you do go a long way towards not having to call `unsubscribe()` when used with `async pipe` by combining and manipulating Observables within your component. You can also often `async pipe` a value into a dumb component's `@Input()` and work with the raw value from that point forward. I find the `ngUnsubscribe()` solution helpful when Observables get very complex but I don't know if it should be the default solution. – seangwright Feb 14 '18 at 17:39
  • I don't understand why do we still need to do `this.ngUnsubscribe.complete();`. No one from outside is referencing this subject . And all the subscriptions which touched that subject,were completed. So why ? – Royi Namir Mar 29 '18 at 20:13
  • 1
    @RoyiNamir The whole idea of this approach is to have a way to clean up all Subscriptions that are being managed manually (not by `AsyncPipe`) in the component. We connect all subscriptions to this one resource (`ngUnsubscribe`) and when it is cleaned up the rest will be too. **But** we still have to clean up `ngUnsubscribe`. If we don't `complete()` `ngUnsubscribe` then it will continue to live on after the component is disposed. – seangwright Mar 29 '18 at 22:20
  • Isnt myobj is released when comp is destroyed? – Royi Namir Mar 30 '18 at 03:53
  • @seangwright you're wrong. No need to complete https://stackoverflow.com/questions/49569089/does-completing-private-subjects-in-angular-necessary?noredirect=1#comment86146143_49569089 – Royi Namir Mar 30 '18 at 06:04
  • @RoyiNamir You are correct. It appears you don't need to `ngUnsubscribe.complete()`. I still think it's a good habit. – seangwright Mar 30 '18 at 20:41
  • Why not use a class variable? `private alive = true` ... `.takeWhile(() => this.alive)` And in the `ngOnDestroy()` set to false: `this.alive = false`. I don't see why we need to use another Subject... – Melroy van den Berg Apr 29 '18 at 01:12
  • 2
    Any news on the Angular docs team publishing their 'undocumented' solution (a year later)? – ElliotSchmelliot May 25 '18 at 17:10
  • 1
    It's worth to mention that the `takeUntil` operator should be the last to avoid leaks with intermediate observables in the operator chain. – NoNameProvided Jun 15 '18 at 06:56
  • 2
    Is this solution still valid for angular 6 with new version of Rxjs? – Anthony Jun 15 '18 at 14:07
  • @NoNameProvided Correct - more details about that in this medium post https://blog.angularindepth.com/rxjs-avoiding-takeuntil-leaks-fb5182d047ef – seangwright Jun 15 '18 at 17:16
  • @Anthony Other than `lettable` operators now being called `pipeable` operators, I believe everything detailed should still apply. You will want to use the `import { takeUntil } from 'rxjs/operators'` instead of the mutating import `import 'rxjs/add/operator/takeUntil';` – seangwright Jun 15 '18 at 17:18
  • 1
    @seangwright Why do I getting this Generic type Subject requires 1 type argument(s) by following this approach, did I missed something? Thanks – Roxy'Pro Jun 29 '18 at 07:19
  • 1
    @Roxy'Pro you just need to add an argument to template `Subject` (even if you are not going to use it), so that you have, e.g., `Subject`. That is: `private ngUnsubscribe: Subject = new Subject();` – Jago Jul 02 '18 at 10:04
  • 1
    I found this guide online which explains it well: https://github.com/ueler/angular-rxjs-unsubscribe – Ynv Feb 10 '20 at 21:07
  • Does not the destroying of the Component also clean up the Subject automatically? Would it not be sufficient to only use .next()? I've worked on a few large stacks where we use this trick, but we don't complete(). Since the Subject is declared as a local variable in the component, and Angular destroys local variables in any component that is destroyed, why is complete() necessary? – Lars Holdaas Feb 27 '20 at 08:00
  • 3
    Is it still in 2020 relevant? Or in Angular 10 something has changed? – tillias Oct 06 '20 at 09:01
  • 1
    @tillias This is still relevant if you are subscribing to Observables in code and you want a consistent way to ensure they are cleaned up when the component/directive are destroyed. However, the recommended approach to Observables is to use the [`| async` pipe](https://angular.io/api/common/AsyncPipe) in the template. Using a state management library like [Akita](https://github.com/datorama/akita) or [NgRx](https://ngrx.io/) will result in most Observables being bound to the template and not subscribed to in code. – seangwright Oct 14 '20 at 01:00
  • _Ben mentions that there are discussions right now to allow Observables to hook into the Angular component lifecycle events (...)_. Does anyone know where/if we'll see the fruit of these discussions? Cheers! – Joel Balmer May 06 '21 at 10:39
  • @seangwright thanks for your answer, but is unsubscribe necessary for angular v.13? or handled? – Mohammad Hossein Ganjyar May 08 '22 at 11:08
  • I have created a demo of subscribe http. You can see the logs that http request is response after navigate to other component! https://angular-subscribe-unsubscribe-sample.stackblitz.io I hope this could give you sight. – Mohammad Hossein Ganjyar May 08 '22 at 12:52
  • Unfortunately, even with unsubscribing the observable still gets fired twice. it seems what's happening is component is not calling ngOnDestroy before the next one is created. so it subscribes again, fires, then unsubscribes on ngOnDestroy and then subscribes on ngOnInit. Unsubscribing on ngOnDestroy does guarantee it gets cleaned up though – dormant Oct 16 '22 at 18:02
  • 1
    "Don't worry about finite ones, RxJs will take care of them." Mostly you're right, BUT some exceptional situations exist, for example: 1) when data is loaded - you track analytics, but user did not wait until data is loaded and navigates away from the page. If do not unsubscribe - analytics would be tracked despite user is already on another page 2) you're loading list of products on /products page, but according to business rules if only 1 product returned - you redirect user on /product/{id} page, if not unsubscribe - redirection will occur despite user is already on another page – Uladzimir Dec 26 '22 at 00:08
146

You don't need to have bunch of subscriptions and unsubscribe manually. Use Subject and takeUntil combo to handle subscriptions like a boss:

import { Subject } from "rxjs"
import { takeUntil } from "rxjs/operators"

@Component({
  moduleId: __moduleName,
  selector: "my-view",
  templateUrl: "../views/view-route.view.html"
})
export class ViewRouteComponent implements OnInit, OnDestroy {
  componentDestroyed$: Subject<boolean> = new Subject()

  constructor(private titleService: TitleService) {}

  ngOnInit() {
    this.titleService.emitter1$
      .pipe(takeUntil(this.componentDestroyed$))
      .subscribe((data: any) => { /* ... do something 1 */ })

    this.titleService.emitter2$
      .pipe(takeUntil(this.componentDestroyed$))
      .subscribe((data: any) => { /* ... do something 2 */ })

    //...

    this.titleService.emitterN$
      .pipe(takeUntil(this.componentDestroyed$))
      .subscribe((data: any) => { /* ... do something N */ })
  }

  ngOnDestroy() {
    this.componentDestroyed$.next(true)
    this.componentDestroyed$.complete()
  }
}

Alternative approach, which was proposed by @acumartini in comments, uses takeWhile instead of takeUntil. You may prefer it, but mind that this way your Observable execution will not be cancelled on ngDestroy of your component (e.g. when you make time consuming calculations or wait for data from server). Method, which is based on takeUntil, doesn't have this drawback and leads to immediate cancellation of request. Thanks to @AlexChe for detailed explanation in comments.

So here is the code:

@Component({
  moduleId: __moduleName,
  selector: "my-view",
  templateUrl: "../views/view-route.view.html"
})
export class ViewRouteComponent implements OnInit, OnDestroy {
  alive: boolean = true

  constructor(private titleService: TitleService) {}

  ngOnInit() {
    this.titleService.emitter1$
      .pipe(takeWhile(() => this.alive))
      .subscribe((data: any) => { /* ... do something 1 */ })

    this.titleService.emitter2$
      .pipe(takeWhile(() => this.alive))
      .subscribe((data: any) => { /* ... do something 2 */ })

    // ...

    this.titleService.emitterN$
      .pipe(takeWhile(() => this.alive))
      .subscribe((data: any) => { /* ... do something N */ })
  }

  ngOnDestroy() {
    this.alive = false
  }
}
metamaker
  • 2,307
  • 2
  • 19
  • 18
  • Why do you use a subject just to keep a bool , while you can use a bool.? – Royi Namir Apr 22 '17 at 21:00
  • 3
    If he just use a bool to keep the state, how to make "takeUntil" works as expected? – Val Apr 24 '17 at 03:38
  • @Val, you are absolutely correct. @Royi, check `takeUntil` documentation to see explanation how it works http://xgrommx.github.io/rx-book/content/observable/observable_instance_methods/takeuntil.html. As parameter it accepts only Observable | Promise. – metamaker Apr 24 '17 at 08:19
  • @acumartini thx for comment! yep, pretty workable solution as well, but it is more about style preference :) you still need to provide each time predicate function to takeWhile and keep track of boolean variable state, which has to be defined somewhere in your component. Same complexity order of solution, though memory usage may be smaller by several bytes (bool var is smaller than Subject<> object). Or did you have something else in your mind? – metamaker Aug 02 '17 at 21:41
  • Nope, your totally right, style preference :) It simplifies the onDestroy step to one line, which is why I prefer it, but both are good! – acumartini Aug 02 '17 at 23:16
  • 8
    I think there is a significant difference between using `takeUntil` and `takeWhile`. The former unsubscribes from the source observable immediately when fired, while the latter unsubscribes only as soon as next value is produced by the source observable. If producing a value by the source observable is a resource consuming operation, choosing between the two may go beyond style preference. See [the plunk](https://plnkr.co/edit/oaR9ULYcKC0cOrKKKLFh) – Alex Che Aug 22 '17 at 16:40
  • 2
    @AlexChe thanks for providing interesting plunk! This is very valid point for general usage of `takeUntil` vs `takeWhile`, however, not for our specific case. When we need to unsubscribe listeners **on component destruction**, we are just checking boolean value like `() => alive` in `takeWhile`, so any time/memory consuming operations are not used and difference is pretty much about styling (ofc, for this specific case). – metamaker Aug 31 '17 at 10:28
  • 3
    @metamaker Say, in our component we subscribe to an `Observable`, which internally mines some crypto-currency and fires a `next` event for an every mined coin, and mining one such coin takes a day. With `takeUntil` we will unsubscribe from the source mining `Observable` immediately once `ngOnDestroy` is called during our component destruction. Thus the mining `Observable` function is able to cancel it's operation immediately during this process. – Alex Che Aug 31 '17 at 14:19
  • 3
    OTOH, if we use `takeWhile`, in the `ngOnDestory` we just set the boolean variable. But the mining `Observable` function might still work for up to one day, and only then during it's `next` call will it realize that there are no subscriptions active and it needs to cancel. – Alex Che Aug 31 '17 at 14:23
  • 2
    During this day unneeded work is done during the mining function, and also your destroyed component is not eligible for garbage collection. – Alex Che Aug 31 '17 at 14:40
  • 2
    `Probably, this.alive = false MAY not be required here` is misinformation. You have a strong reference that will _never_ change from `true` to `false` or `undefined` unless you explicitly set it to undefined. – Steven Liekens Sep 23 '19 at 12:12
  • Should we use `this.destroy$.next()` and `this.destroy$.complete()` in `ngOnDestroy()` when using `takeUntil`? – Jack Dec 26 '20 at 15:30
  • 1
    @Fredrick Yes you should, to end subscription. – Deepak Jan 04 '21 at 13:56
  • 1
    Is this not the same amount of code as doing unsubscribe manually? What's the benefit? – Red2678 Mar 21 '22 at 11:35
123

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();
  }
}
Steven Liekens
  • 13,266
  • 8
  • 59
  • 85
  • 3
    I'm using this approach. Wondering if this is better than using the approach with takeUntil(), like in the accepted answer.. drawbacks ? – Manuel Di Iorio Sep 19 '17 at 20:28
  • 1
    No drawbacks that I'm aware of. I don't think this is better, just different. – Steven Liekens Sep 19 '17 at 20:56
  • 6
    See https://medium.com/@benlesh/rxjs-dont-unsubscribe-6753ed4fda87 for further discussion on the official `takeUntil` approach versus this approach of collecting subscriptions and calling `unsubscribe`. (This approach seems a lot cleaner to me.) – Josh Kelley Mar 29 '18 at 20:31
  • 5
    One small benefit of this answer: you don't have to check if `this.subscriptions` is null – user2023861 Aug 24 '18 at 19:58
  • 5
    Just avoid the chaining of add methods like `sub = subsciption.add(..).add(..)` because in many cases it produces unexpected results https://github.com/ReactiveX/rxjs/issues/2769#issuecomment-345636477 – Evgeniy Generalov Sep 29 '18 at 13:51
  • Another small benefit: you can inspect how many subscriptions are currently active (i.e. not _closed_) by looking at `subscriptions._teardowns` in a debugger. – Steven Liekens Sep 29 '20 at 14:59
  • 3
    I prefer this approach since it is clearer what we're trying to do : unsubscribe. Other approaches intruduce noise in the pipe. – Eva M Dec 21 '22 at 16:13
  • I prefer this approach. Seems cleaner to me. – Alkis Mavridis Feb 27 '23 at 22:52
48

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:

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.

Mouneer
  • 12,827
  • 2
  • 35
  • 45
  • Answer for No. 6 is not quite right. Services are destroyed and their `ngOnDestroy` is called when the service is provided at a level other than the root level e.g. provided explicitly in a component that later gets removed. In these cases you should unsubscribe from the services inner observables – Drenai Sep 09 '18 at 07:50
  • @Drenai, thanks for your comment and politely I don't agree. If a component is destroyed, the component, service and the observable will be all GCed and the unsubscription will be useless in this case unless you keep a reference for the observable anywhere away from the component (Which is not logical to leak the component states globally despite scoping the service to the component) – Mouneer Sep 11 '18 at 16:02
  • If the service being destroyed has a subscription to an observable belonging to another service higher up in the DI hierarchy, then GC won't occur. Avoid this scenario by unsubscribing in `ngOnDestroy`, which is always called when services are destroyed https://github.com/angular/angular/commit/fc034270ced8f17cf17a82d3f8382dcef435b9a6 – Drenai Sep 11 '18 at 22:54
  • Well said @Drenai but I am talking originally about higher level services that live as long as the app is running and never destroyed. But certainly your point is valid regarding to scoped services. So thanks again and I will edit the answer to include a note about scoped services and to eliminate any ambiguity. – Mouneer Sep 29 '18 at 12:47
  • What about @Input() observables ? Do I need to unsubscribe when subscribing in ngOnChanges e.g. ? – Tim Oct 08 '18 at 15:04
  • 3
    @Tim First of all, `Feel free to unsubscribe anyway. It is harmless and never a bad practice.` and regarding your question, it depends. If the child component is initiated multiple times (For example, added inside `ngIf` or being loaded dynamically), you must unsubscribe to avoid adding multiple subscriptions to the same observer. Otherwise no need. But I prefer unsubscribing inside the child component as this makes it more reusable and isolated from how it could be used. – Mouneer Oct 08 '18 at 17:03
  • I wonder if `form.valueChanges` (or statusChanges) could survive and do harm when the component is destroyed. IMO your rule only applies to subscriptions to forms not created in the own component or when the subscriptions are created several times during the live-time. – hgoebl May 27 '19 at 14:55
  • I had the same doubts as @hgoebl. So I performed some experiments in the Chrome Dev Tools. Here is the result: https://stackoverflow.com/a/61699989/4792276. What do you think? – Tuhin Karmakar May 09 '20 at 16:25
19

It depends. If by calling someObservable.subscribe(), you start holding up some resource that must be manually freed-up when the lifecycle of your component is over, then you should call theSubscription.unsubscribe() to prevent memory leak.

Let's take a closer look at your examples:

getHero() returns the result of http.get(). If you look into the angular 2 source code, http.get() creates two event listeners:

_xhr.addEventListener('load', onLoad);
_xhr.addEventListener('error', onError);

and by calling unsubscribe(), you can cancel the request as well as the listeners:

_xhr.removeEventListener('load', onLoad);
_xhr.removeEventListener('error', onError);
_xhr.abort();

Note that _xhr is platform specific but I think it's safe to assume that it is an XMLHttpRequest() in your case.

Normally, this is enough evidence to warrant a manual unsubscribe() call. But according this WHATWG spec, the XMLHttpRequest() is subject to garbage collection once it is "done", even if there are event listeners attached to it. So I guess that's why angular 2 official guide omits unsubscribe() and lets GC clean up the listeners.

As for your second example, it depends on the implementation of params. As of today, the angular official guide no longer shows unsubscribing from params. I looked into src again and found that params is a just a BehaviorSubject. Since no event listeners or timers were used, and no global variables were created, it should be safe to omit unsubscribe().

The bottom line to your question is that always call unsubscribe() as a guard against memory leak, unless you are certain that the execution of the observable doesn't create global variables, add event listeners, set timers, or do anything else that results in memory leaks.

When in doubt, look into the implementation of that observable. If the observable has written some clean up logic into its unsubscribe(), which is usually the function that is returned by the constructor, then you have good reason to seriously consider calling unsubscribe().

Chuanqi Sun
  • 1,123
  • 14
  • 26
13

Angular 2 official documentation provides an explanation for when to unsubscribe and when it can be safely ignored. Have a look at this link:

https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service

Look for the paragraph with the heading Parent and children communicate via a service and then the blue box:

Notice that we capture the subscription and unsubscribe when the AstronautComponent is destroyed. This is a memory-leak guard step. There is no actual risk in this app because the lifetime of a AstronautComponent is the same as the lifetime of the app itself. That would not always be true in a more complex application.

We do not add this guard to the MissionControlComponent because, as the parent, it controls the lifetime of the MissionService.

I hope this helps you.

Community
  • 1
  • 1
Cerny
  • 305
  • 1
  • 4
  • 5
    as a component you never know whether you're a child or not. therefore you should always unsubscribe from subscriptions as best practice. – SeriousM Oct 29 '16 at 17:57
  • 5
    The point about MissionControlComponent is not really about whether it's a parent or not, it's that the component itself provides the service. When MissionControl gets destroyed, so does the service and any references to the instance of the service, thus there is no possibility of a leak. – ender Nov 10 '16 at 20:44
6

Based on : Using Class inheritance to hook to Angular 2 component lifecycle

Another generic approach:

export abstract class UnsubscribeOnDestroy implements OnDestroy {
  protected d$: Subject<any>;

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

    const f = this.ngOnDestroy;
    this.ngOnDestroy = () => {
      f();
      this.d$.next();
      this.d$.complete();
    };
  }

  public ngOnDestroy() {
    // no-op
  }

}

And use :

@Component({
    selector: 'my-comp',
    template: ``
})
export class RsvpFormSaveComponent extends UnsubscribeOnDestroy implements OnInit {

    constructor() {
        super();
    }

    ngOnInit(): void {
      Observable.of('bla')
      .takeUntil(this.d$)
      .subscribe(val => console.log(val));
    }
}
JoG
  • 962
  • 2
  • 11
  • 17
  • 1
    This does NOT work correctly. Please be careful when using this solution. You are missing a `this.componentDestroyed$.next()` call like the accepted solution by sean above... – philn Feb 23 '18 at 12:00
  • @philn Should we use `this.destroy$.next()` and `this.destroy$.complete()` in `ngOnDestroy()` when using `takeUntil`? – Jack Dec 26 '20 at 15:31
  • it nicely works as is. the only missing thing is error handling. if components `ngOnInit` fails (it is `f()` in the code), the `d$` still should emit. try/finally block is needed there – IAfanasov Sep 14 '21 at 07:42
6

For observables that complete directly after emitting the result like AsyncSubject or for example observables from http requests and such you don't need to unsubscribe. It doesn't hurt to to call unsubscribe() for those, but if the observable is closed the unsubscribe method will simply not do anything:

if (this.closed) {
  return;
}

When you have long-lived observables that emit several values over time (like for example a BehaviorSubject or a ReplaySubject) you need to unsubscribe to prevent memory leaks.

You can easily create an observable that completes directly after emitting a result from such long lived observables using a pipe operator. In some answers here the take(1) pipe is mentioned. But I prefer the first() pipe. The difference to take(1) is that it will:

deliver an EmptyError to the Observer's error callback if the Observable completes before any next notification was sent.

Another advantage of the first pipe is that you can pass a predicate that will help you to return the first value that satisfies certain criteria:

const predicate = (result: any) => { 
  // check value and return true if it is the result that satisfies your needs
  return true;
}
observable.pipe(first(predicate)).subscribe(observer);

First will complete directly after emitting the first value (or when passing a function argument the first value that satisfies your predicate) so there is no need to unsubscribe.

Sometimes you are not sure about whether you have a long lived observable or not. I am not saying it is good practice but you could then always add the first pipe just to make sure you won't need to manually unsubscribe. Adding an additional first pipe on an observable that will emit only one value doesn't hurt.

During development you can use the single pipe that will fail if source observable emits several events. This can help you to explore the type of observable and whether it is necessary to unsubscribe from it or not.

observable.pipe(single()).subscribe(observer);

The first and single seem very similar, both pipes can take an optional predicate but the differences are important and nicely summarized in this stackoverflow answer here:

First

Will emit as soon as first item appears. Will complete right after that.

Single

Will fail if source observable emits several events.


Note I tried to be as accurate and complete as possible in my answer with references to the official documentation, but please comment if something important is missing...

Community
  • 1
  • 1
Wilt
  • 41,477
  • 12
  • 152
  • 203
5

A Subscription essentially just has an unsubscribe() function to release resources or cancel Observable executions. In Angular, we have to unsubscribe from the Observable when the component is being destroyed. Luckily, Angular has a ngOnDestroy hook that is called before a component is destroyed, this enables devs to provide the cleanup crew here to avoid hanging subscriptions, open portals, and what nots that may come in the future to bite us in the back

@Component({...})
export class AppComponent implements OnInit, OnDestroy {
    subscription: Subscription 
    ngOnInit () {
        var observable = Rx.Observable.interval(1000);
        this.subscription = observable.subscribe(x => console.log(x));
    }
    ngOnDestroy() {
        this.subscription.unsubscribe()
    }
}

We added ngOnDestroy to our AppCompoennt and called unsubscribe method on the this.subscription Observable

If there are multiple subscriptions:

@Component({...})
export class AppComponent implements OnInit, OnDestroy {
    subscription1$: Subscription
    subscription2$: Subscription 
    ngOnInit () {
        var observable1$ = Rx.Observable.interval(1000);
        var observable2$ = Rx.Observable.interval(400);
        this.subscription1$ = observable.subscribe(x => console.log("From interval 1000" x));
        this.subscription2$ = observable.subscribe(x => console.log("From interval 400" x));
    }
    ngOnDestroy() {
        this.subscription1$.unsubscribe()
        this.subscription2$.unsubscribe()
    }
}
Yogesh Waghmare
  • 941
  • 10
  • 10
4

Since seangwright's solution (Edit 3) appears to be very useful, I also found it a pain to pack this feature into base component, and hint other project teammates to remember to call super() on ngOnDestroy to activate this feature.

This answer provide a way to set free from super call, and make "componentDestroyed$" a core of base component.

class BaseClass {
    protected componentDestroyed$: Subject<void> = new Subject<void>();
    constructor() {

        /// wrap the ngOnDestroy to be an Observable. and set free from calling super() on ngOnDestroy.
        let _$ = this.ngOnDestroy;
        this.ngOnDestroy = () => {
            this.componentDestroyed$.next();
            this.componentDestroyed$.complete();
            _$();
        }
    }

    /// placeholder of ngOnDestroy. no need to do super() call of extended class.
    ngOnDestroy() {}
}

And then you can use this feature freely for example:

@Component({
    selector: 'my-thing',
    templateUrl: './my-thing.component.html'
})
export class MyThingComponent extends BaseClass implements OnInit, OnDestroy {
    constructor(
        private myThingService: MyThingService,
    ) { super(); }

    ngOnInit() {
        this.myThingService.getThings()
            .takeUntil(this.componentDestroyed$)
            .subscribe(things => console.log(things));
    }

    /// optional. not a requirement to implement OnDestroy
    ngOnDestroy() {
        console.log('everything works as intended with or without super call');
    }

}
Val
  • 21,938
  • 10
  • 68
  • 86
4

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<T> {
    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: '<h3>Subscribing component is active</h3>',
})
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;
  }
}
Richard Matsen
  • 20,671
  • 3
  • 43
  • 77
4

It's always recommended to make an unsubscription from your observable subscriptions for performance reason to avoid memory leaks , and there different ways of doing that ,

By the way I read most of the answers, and I did"t find someone who is talking about the async pipe, it's recommended Rxjs pattern with Angular apps because its provides automatically subscription and subscription when leaving the component who will be destroyed :

Please find an example how it can be implemented

app.compoennt.ts:

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';

import { BookService } from './book.service';
import { Book } from './book';

@Component({
   selector: 'app-observable',
   templateUrl: './observable.component.html'
})
export class AppComponent implements OnInit { 
   books$: Observable<Book[]>
   constructor(private bookService: BookService) { }
   ngOnInit(): void {
        this.books$ = this.bookService.getBooksWithObservable();
   }
} 

app.compoennt.html:

<h3>AsyncPipe with Promise using NgFor</h3>
<ul>
  <li *ngFor="let book of books$ | async" >
    Id: {{book?.id}}, Name: {{book?.name}}
  </li>
</ul>
Rebai Ahmed
  • 1,509
  • 1
  • 14
  • 21
3

I tried seangwright's solution (Edit 3)

That is not working for Observable that created by timer or interval.

However, i got it working by using another approach:

import { Component, OnDestroy, OnInit } from '@angular/core';
import 'rxjs/add/operator/takeUntil';
import { Subject } from 'rxjs/Subject';
import { Subscription } from 'rxjs/Subscription';
import 'rxjs/Rx';

import { MyThingService } from '../my-thing.service';

@Component({
   selector: 'my-thing',
   templateUrl: './my-thing.component.html'
})
export class MyThingComponent implements OnDestroy, OnInit {
   private subscriptions: Array<Subscription> = [];

  constructor(
     private myThingService: MyThingService,
   ) { }

  ngOnInit() {
    const newSubs = this.myThingService.getThings()
        .subscribe(things => console.log(things));
    this.subscriptions.push(newSubs);
  }

  ngOnDestroy() {
    for (const subs of this.subscriptions) {
      subs.unsubscribe();
   }
 }
}
Jeff Tham
  • 106
  • 1
  • 5
3

You usually need to unsubscribe when the components get destroyed, but Angular is going to handle it more and more as we go, for example in new minor version of Angular4, they have this section for routing unsubscribe:

Do you need to unsubscribe?

As described in the ActivatedRoute: the one-stop-shop for route information section of the Routing & Navigation page, the Router manages the observables it provides and localizes the subscriptions. The subscriptions are cleaned up when the component is destroyed, protecting against memory leaks, so you don't need to unsubscribe from the route paramMap Observable.

Also the example below is a good example from Angular to create a component and destroy it after, look at how component implements OnDestroy, if you need onInit, you also can implements it in your component, like implements OnInit, OnDestroy

import { Component, Input, OnDestroy } from '@angular/core';  
import { MissionService } from './mission.service';
import { Subscription }   from 'rxjs/Subscription';

@Component({
  selector: 'my-astronaut',
  template: `
    <p>
      {{astronaut}}: <strong>{{mission}}</strong>
      <button
        (click)="confirm()"
        [disabled]="!announced || confirmed">
        Confirm
      </button>
    </p>
  `
})

export class AstronautComponent implements OnDestroy {
  @Input() astronaut: string;
  mission = '<no mission announced>';
  confirmed = false;
  announced = false;
  subscription: Subscription;

  constructor(private missionService: MissionService) {
    this.subscription = missionService.missionAnnounced$.subscribe(
      mission => {
        this.mission = mission;
        this.announced = true;
        this.confirmed = false;
    });
  }

  confirm() {
    this.confirmed = true;
    this.missionService.confirmMission(this.astronaut);
  }

  ngOnDestroy() {
    // prevent memory leak when component destroyed
    this.subscription.unsubscribe();
  }
}
Alireza
  • 100,211
  • 27
  • 269
  • 172
  • 4
    Confused. What are you saying here? You(Angular recent docs/notes) seem to say that Angular takes care of it and then later to confirm that unsubscribe is a good pattern. Thanks. – jamie Oct 18 '17 at 06:51
3

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.

Mau Muñoz
  • 101
  • 1
  • 5
3

In case unsubscribe is needed the following operator for observable pipe method can be used

import { Observable, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { OnDestroy } from '@angular/core';

export const takeUntilDestroyed = (componentInstance: OnDestroy) => <T>(observable: Observable<T>) => {
  const subjectPropertyName = '__takeUntilDestroySubject__';
  const originalOnDestroy = componentInstance.ngOnDestroy;
  const componentSubject = componentInstance[subjectPropertyName] as Subject<any> || new Subject();

  componentInstance.ngOnDestroy = (...args) => {
    originalOnDestroy.apply(componentInstance, args);
    componentSubject.next(true);
    componentSubject.complete();
  };

  return observable.pipe(takeUntil<T>(componentSubject));
};

it can be used like this:

import { Component, OnDestroy, OnInit } from '@angular/core';
import { Observable } from 'rxjs';

@Component({ template: '<div></div>' })
export class SomeComponent implements OnInit, OnDestroy {

  ngOnInit(): void {
    const observable = Observable.create(observer => {
      observer.next('Hello');
    });

    observable
      .pipe(takeUntilDestroyed(this))
      .subscribe(val => console.log(val));
  }

  ngOnDestroy(): void {
  }
}

The operator wraps ngOnDestroy method of component.

Important: the operator should be the last one in observable pipe.

Oleg Polezky
  • 1,006
  • 14
  • 13
2

I like the last two answers, but I experienced an issue if the the subclass referenced "this" in ngOnDestroy.

I modified it to be this, and it looks like it resolved that issue.

export abstract class BaseComponent implements OnDestroy {
    protected componentDestroyed$: Subject<boolean>;
    constructor() {
        this.componentDestroyed$ = new Subject<boolean>();
        let f = this.ngOnDestroy;
        this.ngOnDestroy = function()  {
            // without this I was getting an error if the subclass had
            // this.blah() in ngOnDestroy
            f.bind(this)();
            this.componentDestroyed$.next(true);
            this.componentDestroyed$.complete();
        };
    }
    /// placeholder of ngOnDestroy. no need to do super() call of extended class.
    ngOnDestroy() {}
}
Aniruddha Das
  • 20,520
  • 23
  • 96
  • 132
  • you need to use the arrow function in order to bind the 'this': `this.ngOnDestroy = () => { f.bind(this)(); this.componentDestroyed$.complete(); };` – Damsorian Sep 14 '17 at 16:00
2

Another short addition to the above mentioned situations is:

  • Always unsubscribe, when new values in the subscribed stream is no more required or don't matter, it will result in way less number of triggers and increase in performance in a few cases. Cases such as components where the subscribed data/event no more exists or a new subscription to an all new stream is required (refresh, etc.) is a good example for unsubscription.
Krishna Ganeriwal
  • 1,903
  • 19
  • 17
2

in SPA application at ngOnDestroy function (angular lifeCycle) For each subscribe you need to unsubscribe it. advantage => to prevent the state from becoming too heavy.

for example: in component1 :

import {UserService} from './user.service';

private user = {name: 'test', id: 1}

constructor(public userService: UserService) {
    this.userService.onUserChange.next(this.user);
}

in service:

import {BehaviorSubject} from 'rxjs/BehaviorSubject';

public onUserChange: BehaviorSubject<any> = new BehaviorSubject({});

in component2:

import {Subscription} from 'rxjs/Subscription';
import {UserService} from './user.service';

private onUserChange: Subscription;

constructor(public userService: UserService) {
    this.onUserChange = this.userService.onUserChange.subscribe(user => {
        console.log(user);
    });
}

public ngOnDestroy(): void {
    // note: Here you have to be sure to unsubscribe to the subscribe item!
    this.onUserChange.unsubscribe();
}
mojtaba ramezani
  • 1,461
  • 16
  • 15
2

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);
}
Pratiyush
  • 51
  • 3
2

With Angular 16, a new function has been introduced to ease the destruction of the observable takeUntilDestroyed.

data$ = http.get('...').pipe(takeUntilDestroyed()).subscribe(...);

By default, it should be called inside the constructor. To use it elsewhere, then DestroyRef is necessary.

destroyRef = inject(DestroyRef);

ngOnInit(){
   data$ = http.get('...').subscribe(...)

   this.destoryRef.onDestroy(() => {
      data$.unsubscribe()
   })
}

Wandrille
  • 6,267
  • 3
  • 20
  • 43
1

The SubSink package, an easy and consistent solution for unsubscribing

As nobody else has mentioned it, I want to recommend the Subsink package created by Ward Bell: https://github.com/wardbell/subsink#readme.

I have been using it on a project were we are several developers all using it. It helps a lot to have a consistent way that works in every situation.

SnorreDan
  • 2,740
  • 1
  • 14
  • 17
1

Here is my take on this issue, keeping my life simple I have chosen the manual way of unsubscribing the subscription when the component get destroyed.

For this I have created a class named Subscriptor, which mostly contains static members namely:

  • A private variable subscriptions - which holds all the supplied subscriptions
  • A subscription setter - which push every new subscription to subscriptions array
  • An unsubscribe method - which unsubscribe every subscriptions the subscriptions array contains if defined, and empty out the subscriptions array

subscriptor.ts

import { Subscription } from "rxjs";

export class Subscriptor {
    private static subscriptions: Subscription[] = [];

    static set subscription(subscription: Subscription) {
        Subscriptor.subscriptions.push(subscription);
    }

    static unsubscribe() {
        Subscriptor.subscriptions.forEach(subscription => subscription ? subscription.unsubscribe() : 0);
        Subscriptor.subscriptions = [];
    }
}

Usage inside a component is as follows:

When you want to subscribe any service, simply put the subscription to the Subscriptor's setter.

ngOnInit(): void {
    Subscriptor.subscription = this.userService.getAll().subscribe(users => this.users = users);
    Subscriptor.subscription = this.categoryService.getAll().subscribe(categories => this.categories = categories);
    Subscriptor.subscription = this.postService.getAll().subscribe(posts => this.posts = posts);
}

When you want to unsubscribe any service, simply call the unsubscribe method of Subscriptor.

ngOnDestroy(): void {
    Subscriptor.unsubscribe();
}
assylias
  • 321,522
  • 82
  • 660
  • 783
Zaki Mohammed
  • 969
  • 14
  • 24
1

DisposeBag

The idea was inspired by RxSwift's DisposeBag, so I decided to develop a similar yet simple structure.

DisposeBag is a data structure that holds a reference to all open subscriptions. It facilitates the disposal of the subscription in our components while providing us with APIs to track the state of open subscriptions.

Advantages

Very simple API, makes your code look simple and small. Provides API for tracking the status of open subscriptions (allows you to show indeterminate progress bar) No dependency injections/packages.

Usage

In component:

@Component({
  selector: 'some-component',
  templateUrl: './some-component.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class SomeComponent implements OnInit, OnDestroy {

  public bag = new DisposeBag()
  
  constructor(private _change: ChangeDetectorRef) {
  }

  ngOnInit(): void {

    // an observable that takes some time to finish such as an api call.
    const aSimpleObservable = of(0).pipe(delay(5000))

    // this identifier allows us to track the progress for this specific subscription (very useful in template)
    this.bag.subscribe("submission", aSimpleObservable, () => { 
      this._change.markForCheck() // trigger UI change
     })
  }

  ngOnDestroy(): void {
    // never forget to add this line.
    this.bag.destroy()
  }
}

In template:


<!-- will be shown as long as the submission subscription is open -->
<span *ngIf="bag.inProgress('submission')">submission in progress</span>

<!-- will be shown as long as there's an open subscription in the bag  -->
<span *ngIf="bag.hasInProgress">some subscriptions are still in progress</span>

Implementation

import { Observable, Observer, Subject, Subscription, takeUntil } from "rxjs";


/**
 * This class facilitates the disposal of the subscription in our components.
 * instead of creating _unsubscribeAll and lots of boilerplates to create different variables for Subscriptions; 
 * you can just easily use subscribe(someStringIdentifier, observable, observer). then you can use bag.inProgress() with
 * the same someStringIdentifier on you html or elsewhere to determine the state of the ongoing subscription.
 *
 *  don't forget to add onDestroy() { this.bag.destroy() }
 * 
 *  Author: Hamidreza Vakilian (hvakilian1@gmail.com)
 * @export
 * @class DisposeBag
 */
export class DisposeBag {
    private _unsubscribeAll: Subject<any> = new Subject<any>();

    private subscriptions = new Map<string, Subscription>()


    /**
     * this method automatically adds takeUntil to your observable, adds it to a private map.
     * this method enables inProgress to work. don't forget to add onDestroy() { this.bag.destroy() }
     *
     * @template T
     * @param {string} id
     * @param {Observable<T>} obs
     * @param {Partial<Observer<T>>} observer
     * @return {*}  {Subscription}
     * @memberof DisposeBag
     */
    public subscribe<T>(id: string, obs: Observable<T>, observer: Partial<Observer<T>> | ((value: T) => void)): Subscription {
        if (id.isEmpty()) {
            throw new Error('disposable.subscribe is called with invalid id')
        }
        if (!obs) {
            throw new Error('disposable.subscribe is called with an invalid observable')
        }

        /* handle the observer */
        let subs: Subscription
        if (typeof observer === 'function') {
            subs = obs.pipe(takeUntil(this._unsubscribeAll)).subscribe(observer)
        } else if (typeof observer === 'object') {
            subs = obs.pipe(takeUntil(this._unsubscribeAll)).subscribe(observer)
        } else {
            throw new Error('disposable.subscribe is called with an invalid observer')
        }

        /* unsubscribe from the last possible subscription if in progress. */
        let possibleSubs = this.subscriptions.get(id)
        if (possibleSubs && !possibleSubs.closed) {
            console.info(`Disposebag: a subscription with id=${id} was disposed and replaced.`)
            possibleSubs.unsubscribe()
        }

        /* store the reference in the map */
        this.subscriptions.set(id, subs)

        return subs
    }


    /**
     * Returns true if any of the registered subscriptions is in progress.
     *
     * @readonly
     * @type {boolean}
     * @memberof DisposeBag
     */
    public get hasInProgress(): boolean {
        return Array.from(this.subscriptions.values()).reduce(
            (prev, current: Subscription) => { 
                return prev || !current.closed }
            , false)
    }

    /**
     * call this from your template or elsewhere to determine the state of each subscription.
     *
     * @param {string} id
     * @return {*} 
     * @memberof DisposeBag
     */
    public inProgress(id: string) {
        let possibleSubs = this.subscriptions.get(id)
        if (possibleSubs) {
            return !possibleSubs.closed
        } else {
            return false
        }
    }


    /**
     * Never forget to call this method in your onDestroy() method of your components.
     *
     * @memberof DisposeBag
     */
    public destroy() {
        this._unsubscribeAll.next(null);
        this._unsubscribeAll.complete();
    }
}

  • `*ngIf="bag.inProgress(...)"` (and `bag.hasInProgress()`) should be implemented as pipes. I'm curious if the pipes could be pure pipes...based on the current implementation, it might be difficult. – JWess Jun 15 '22 at 16:09
0

You can use latest Subscription class to unsubscribe for the Observable with not so messy code.

We can do this with normal variable but it will be override the last subscription on every new subscribe so avoid that, and this approach is very much useful when you are dealing with more number of Obseravables, and type of Obeservables like BehavoiurSubject and Subject

Subscription

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.

you can use this in two ways,

  • you can directly push the subscription to Subscription Array

     subscriptions:Subscription[] = [];
    
     ngOnInit(): void {
    
       this.subscription.push(this.dataService.getMessageTracker().subscribe((param: any) => {
                //...  
       }));
    
       this.subscription.push(this.dataService.getFileTracker().subscribe((param: any) => {
            //...
        }));
     }
    
     ngOnDestroy(){
        // prevent memory leak when component destroyed
        this.subscriptions.forEach(s => s.unsubscribe());
      }
    
  • using add() of Subscription

    subscriptions = new Subscription();
    
    this.subscriptions.add(subscribeOne);
    this.subscriptions.add(subscribeTwo);
    
    ngOnDestroy() {
      this.subscriptions.unsubscribe();
    }
    

A Subscription can hold child subscriptions and safely unsubscribe them all. This method handles possible errors (e.g. if any child subscriptions are null).

Hope this helps.. :)

Ganesh
  • 5,808
  • 2
  • 21
  • 41
0

--- Update Angular 9 and Rxjs 6 Solution

  1. Using unsubscribe at ngDestroy lifecycle of Angular Component
class SampleComponent implements OnInit, OnDestroy {
  private subscriptions: Subscription;
  private sampleObservable$: Observable<any>;

  constructor () {}

  ngOnInit(){
    this.subscriptions = this.sampleObservable$.subscribe( ... );
  }

  ngOnDestroy() {
    this.subscriptions.unsubscribe();
  }
}
  1. Using takeUntil in Rxjs
class SampleComponent implements OnInit, OnDestroy {
  private unsubscribe$: new Subject<void>;
  private sampleObservable$: Observable<any>;

  constructor () {}

  ngOnInit(){
    this.subscriptions = this.sampleObservable$
    .pipe(takeUntil(this.unsubscribe$))
    .subscribe( ... );
  }

  ngOnDestroy() {
    this.unsubscribe$.next();
    this.unsubscribe$.complete();
  }
}
  1. for some action that you call at ngOnInit that just happen only one time when component init.
class SampleComponent implements OnInit {

  private sampleObservable$: Observable<any>;

  constructor () {}

  ngOnInit(){
    this.subscriptions = this.sampleObservable$
    .pipe(take(1))
    .subscribe( ... );
  }
}

We also have async pipe. But, this one use on the template (not in Angular component).

Hoang Subin
  • 6,610
  • 6
  • 37
  • 56
0

In my case I am using a variation of the solution proposed by @seanwright:
https://github.com/NetanelBasal/ngx-take-until-destroy

It's a file used in the ngx-rocket / starter-kit project. You can access it here until-destroyed.ts

The component would look like so

/**
 * RxJS operator that unsubscribe from observables on destory.
 * Code forked from https://github.com/NetanelBasal/ngx-take-until-destroy
 *
 * IMPORTANT: Add the `untilDestroyed` operator as the last one to
 * prevent leaks with intermediate observables in the
 * operator chain.
 *
 * @param instance The parent Angular component or object instance.
 * @param destroyMethodName The method to hook on (default: 'ngOnDestroy').
 */
import { untilDestroyed } from '../../core/until-destroyed';

@Component({
  selector: 'app-example',
  templateUrl: './example.component.html'
})
export class ExampleComponent implements OnInit, OnDestroy {

  ngOnInit() {
    interval(1000)
        .pipe(untilDestroyed(this))
        .subscribe(val => console.log(val));

    // ...
  }


  // This method must be present, even if empty.
  ngOnDestroy() {
    // To protect you, an error will be thrown if it doesn't exist.
  }
}
user9869932
  • 6,571
  • 3
  • 55
  • 49
0

A lot of great answers here...

Let me add another alternative:

import { interval    } from "rxjs";
import { takeUntil   } from "rxjs/operators";
import { Component   } from "@angular/core";
import { Destroyable } from "@bespunky/angular-zen/core";

@Component({
    selector: 'app-no-leak-demo',
    template: ' Destroyable component rendered. Unload me and watch me cleanup...'
})
export class NoLeakComponent extends Destroyable
{
    constructor()
    {
        super();

        this.subscribeToInterval();
    }

    private subscribeToInterval(): void
    {
        const value    = interval(1000);
        const observer = {
            next    : value => console.log(` Destroyable: ${value}`),
            complete: ()    => console.log(' Observable completed.')
        };

        // ==== Comment one and uncomment the other to see the difference ====
        
        // Subscribe using the inherited subscribe method
         this.subscribe(value, observer);

        // ... or pipe-in the inherited destroyed subject
        //value.pipe(takeUntil(this.destroyed)).subscribe(observer);
    }
}

Live Example

What's happening here

The component/service extends Destroyable (which comes from a library called @bespunky/angular-zen).

The class can now simply use this.subscribe() or takeUntil(this.destroyed) without any additional boilerplate code.

To install the library use:
> npm install @bespunky/angular-zen

Shy Agam
  • 1,285
  • 1
  • 13
  • 37
0

There are 2 ways to unsubscribe observable in Angular

One way: it's always better to unsubscribe the observable$ which we subscribed using .subscribe in the component.ts The lifecycle hook that triggers when we leave the component is ngOnDestroy(), so unsubscribe all our observables inside ngOnDestroy() ex: observable$?.unsubscribe();

second way: subscribe observables using async pipe in the template, this will automatically subscribe and unsubscribe the observable once it completed ex: <div *ngIf="observable$ | async"> </div>

Sreehari Ballampalli
  • 3,404
  • 3
  • 12
  • 19