7
votes

As described in this thread, 'official' solution to unsubscribe from Observables in Angular 5+ in general is using takeUntil. So far, so good. My question is, does this also apply if the Observable I am subscribed to is actually a Subject?

2
Yes, it doesn't matter because Subject extends Observable class. This applies to EventEmitter as well because it extends Subject class. - martin
as martin said, yes. Basically anytime you subscribe to something, you should destroy that subscription (unsubscribe) at some point in time, to avoid memory leaks, no matter what you are subscribing to. - David Anthony Acosta
To add to above, OnDestroy is a great thing to implement for destroying subscriptions in components. brianflove.com/2016/12/11/anguar-2-unsubscribe-observables - ShellNinja
Also, put takeUntil as last operator to avoid messing up the unsubscribe: blog.angularindepth.com/… - Davy

2 Answers

21
votes

Once you call .subscribe() on anything (Subjects too), something needs to make sure the subscription gets unsubscribed.

Dealing with finite Observables: If you subscribe to a finite observable (meaning an observable that has a finite/limited sequence), the last message will send an end signal and the subscription will be canceled automatically. Examples of this are:

Observable.of(100)
Observable.from([1,2,3,4])

Examples of infinite observables are:

Observable.fromEvent(document, 'click')
Observable.timer(1000)

Calling/piping .first(), .take(number) or .takeWhile(condition that will evaluate to false at some point) or takeUntil(observable that emits a value) on an observable will all turn an otherwise infinite observable into a finite one.

Stop calling .subscribe(): Another popular method of not having to unsubscribe is by not subscribing in the first place. This might sound stupid, since when would you want an observable that you do not subscribe to? Well if you only need to pass some data to your view/html template, piping that observable into the async pipe will pass the unsubscribing issue to the async pipe itself.

Typical examples in the html template:

<h1>Editing {{ infiniteObservable$ | async }}<h1>
<li *ngFor="let user of userObservable$ | async as users; index as i; first as isFirst">
   {{i}}/{{users.length}}. {{user}} <span *ngIf="isFirst">default</span>
</li>

Manually unsubscribing: Lastly, you can choose to keep references to all subscriptions. You don't have to keep a variable pointing to each subscription, it's easier to just use a single Subscription object to keep track of all the subscriptions, and then unsubscribe to all of them at once. Here is an example:

const subscriptions = new Subscription();
subscriptions.add(observable1$.subscribe());
subscriptions.add(observable2$.subscribe());
subscriptions.unsubscribe();

Quick summerize, how to handle unsubscriptions, any of the below methods:

  1. Turn infinite observables into finite ones, hereby removing the need to unsubscribe (use .takeUntil(this.destroyed$) and do this.destroyed$.emit() in ngOnDestroy()).
  2. Avoid subscribing, and passing the observable though the async pipe.
  3. Keep a reference to any subscriptions and call .unsubscribe() in the ngOnDestroy() method.

Personally i tend to only use one of the two first methods.

1
votes

I have something to add. Subject stores the subscribers internally (Observable does too). If the Subject is part of your component (created inside, stored as property or in a closure) the subject and it's subscriptions are garbage collected with the component itself.

But this is a special case and one should be very careful with it: everything must be contained in the component.

It is e.g. safe to not unsubscribe from a FormControl.valueChanges observable if it is used in the component only.

But to be on the safe side, and don't want to think about it, just use takeUntil.