When we use RxJS, it's standard practice to subscribe to Observables. By doing so, we create a Subscription
. This object provides us with some methods that will aid in managing these subscriptions. This is very important, and is something that should not be overlooked!
Why do we care about subscription management?
If we do not put some thought into how we manage and clean up the subscriptions we create, we can cause an array of problems in our applications. This is due to how the Observer Pattern is implemented.
When an Observable emits a new value, its Observers execute code that was set up during the subscription. For example:
obs$.subscribe(data => doSomethingWithDataReceived(data));
If we do not manage this subscription, every time obs$
emits a new value doSomethingWithDataReceived
will be called.
Let's say this code is set up on the Home View of our App. It should only ever be run when the user is on the Home View. Without managing this subscription correctly when the user navigates to a new view in the App, doSomethingWithDataReceived
could still be called, potentially causing unexpected results, errors or even hard-to-track bugs.
So what do we mean by Subscription Management?
Essentially, subscription management revolves around knowing when to complete
or unsubscribe
from an Observable, to prevent incorrect code from being executed, especially when we would not expect it to be executed.
We can refer to this management of subscriptions as cleaning up active subscriptions.
How can we clean up subscriptions?
So, now that we know that managing subscriptions are an essential part of working with RxJS, what methods are available for us to manage them?
Unsubscribing Manually
One method we can use is to unsubscribe manually from active subscriptions when we no longer require them. RxJS provides us with a convenient method to do this. It lives on the Subscription
object and is called .unsubscribe()
.
If we take the example we had above; we can see how easy it is to unsubscribe when we need to:
let homeViewSubscription = null;
function onEnterView() {
homeViewSubscription = obs$.subscribe(data => doSomethingWithDataReceived(data));
}
function onLeaveView() {
homeViewSubscription.unsubscribe();
}
- We create a variable to store the subscription.
- We store the subscription in a variable when we enter the view.
- We unsubscribe from the subscription when we leave the view preventing
doSomethingWithDataReceived()
from being executed when we don't need it.
This is great; however, when working with RxJS, you will likely have more than one subscription. Calling unsubscribe
for each of them could get tedious. A solution I have seen many codebases employ is to store an array of active subscriptions, loop through this array, unsubscribing from each when required.
Let's modify the example above to see how we could do this:
const homeViewSubscriptions = [];
function onEnterView() {
homeViewSubscriptions.push(
obs$.subscribe(data => doSomethingWithDataReceived(data)),
anotherObs$.subscribe(user => updateUserData(user))
);
}
function onLeaveView() {
homeViewSubscriptions.forEach(subscription => subscription.unsubscribe());
}
- We create an array to store the subscriptions.
- We add each subscription to the array when we enter the view.
- We loop through and unsubscribe from the subscriptions in the array.
These are both valid methods of managing subscriptions and can and should be employed when necessary. There are other options. However, that can add a bit more resilience to your management of subscriptions.
Using Operators
RxJS provides us with some operators that will clean up the subscription automatically when a condition is met, meaning we do not need to worry about setting up a variable to track our subscriptions.
Let's take a look at some of these!
first
The first
operator will take only the first value emitted, or the first value that meets the specified criteria. Then it will complete, meaning we do not have to worry about manually unsubscribing. Let's see how we would use this with our example above:
function onEnterView() {
obs$.pipe(first())
.subscribe(data => doSomethingWithDataReceived(data))
}
When obs$
emits a value, first()
will pass the value to doSomethingWithDataReceived
and then unsubscribe!
take
The take
operator allows us to specify how many values we want to receive from the Observable before we unsubscribe. This means that when we receive the specified number of values, take
will automatically unsubscribe!
function onEnterView() {
obs$.pipe(take(5))
.subscribe(data => doSomethingWithDataReceived(data))
}
Once obs$
has emitted five values, take
will unsubscribe automatically!
takeUntil
The takeUntil
operator provides us with an option to continue to receive values from an Observable until a different, notifier
Observable emits a new value.
Let's see it in action:
const notifier$ = new Subject();
function onEnterView() {
obs$.pipe(takeUntil(notifier$)).subscribe(data => doSomethingWithDataReceived(data))
}
function onLeaveView() {
notifier$.next();
notifier$.complete();
}
- We create a
notifier$
Observable using a Subject. (You can learn more about Creating Observables here.) - We use
takeUntil
to state that we want to receive values untilnotifier$
emits a value - We tell
notifier$
to emit a value and complete _(we need to cleannotifer$
up ourselves) when we leave the view, allowing our original subscription to be unsubscribed.
takeWhile
Another option is the takeWhile
operator. It allows us to continue receiving values whilst a specified condition remains true. Once it becomes false, it will unsubscribe automatically.
function onEnterView() {
obs$
.pipe(takeWhile(data => data.finished === false))
.subscribe(data => doSomethingWithDataReceived(data))
}
In the example above we can see that whilst the property finished
on the data emitted is false
we will continue to receive values. When it turns to true
, takeWhile
will unsubscribe!
BONUS: With Angular
RxJS and Angular go hand-in-hand, even if the Angular team has tried to make the framework as agnostic as possible. From this, we usually find ourselves having to manage subscriptions in some manner.
async
Pipe
Angular itself provides one option for us to manage subscriptions, the async
pipe. This pipe will subscribe to an Observable in the template, and when the template is destroyed, it will unsubscribe from the Observable automatically. It's very simple to use:
<div *ngIf="obs$ | async as data">
{{ data | json }}
</div>
By using the as data
, we set the value emitted from the Observable to a template variable called data
, allowing us to use it elsewhere in the children nodes to the div
node.
When the template is destroyed, Angular will handle the cleanup!
untilDestroyed
Another option comes from a third-party library developed by Netanel Basal. It's called until-destroyed
, and it provides us with multiple options for cleaning up subscriptions in Angular when Angular destroys a Component.
We can use it similarly to takeUntil
:
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
@UntilDestroy()
@Component({
selector: 'home'
})
export class HomeComponent implements OnInit {
ngOnInit() {
obs$
.pipe(untilDestroyed(this))
.subscribe(data => this.doSoemthingWithDataReceived(data));
}
}
It can also find which properties in your component are Subscription
objects and automatically unsubscribe from them:
@UntilDestroy({ checkProperties: true })
@Component({
selector: 'home'
})
export class HomeComponent {
subscription = obs$
.pipe(untilDestroyed(this))
.subscribe(data => this.doSoemthingWithDataReceived(data));
}
This little library can be beneficial for managing subscriptions for Angular!
When should we employ one of these methods?
The simple answer to this question would be:
When we no longer want to execute code when the Observable emits a new value
But that doesn't give an example use-case.
- We have covered one example use case in this article: when you navigate away from a view in your SPA.
- In Angular, you'd want to use it when you destroy Components.
- Combined with State Management, you could use it only to select a slice of state once that you do not expect to change over the lifecycle of the application.
- Generally, you'd want to do it when a condition is met. This condition could be anything from the first click a user makes to when a certain length of time has passed.
Next time you're working with RxJS and subscriptions, think about when you no longer want to receive values from an Observable, and ensure you have code that will allow this to happen!
This Dot Labs is a modern web consultancy focused on helping companies realize their digital transformation efforts. For expert architectural guidance, training, or consulting in React, Angular, Vue, Web Components, GraphQL, Node, Bazel, or Polymer, visit thisdotlabs.com.
This Dot Media is focused on creating an inclusive and educational web for all. We keep you up to date with advancements in the modern web through events, podcasts, and free content. To learn, visit thisdot.co.
Top comments (1)
That's actually quite a useful pattern. I remember unsubscribing manually throughout my code