It might be know to you already, that there are situations where you actually want to unsubscribe from your
There are several ways to do this. In several projects, I stumbled across a mixture of using
takeUntil. This raises the question, why are there two of that, which sounds similar and act similarly?
Well, let's take a look at a quick example. The first code snippet we are looking at is using
takeWhile to unsubscribe from an
In this example I have two different
Observables. The first is created using the interval operator. This will emit notifications till the condition passed to
takeWhile is falsy. Inside the
takeWhile we use a boolean variable describing if the user has already clicked or not. As soon has one clicks somewhere in the screen, we unsubscribe from our
interval-Observable. To determine if the user already clicked we used a second
Observable created with the fromEvent operator. Additionally we used the tap operator, to log the notifications in the console. We can see that our Observable is unsubscribed as soon as there is no new log coming in.
From a high-level perspective, the code snippets don't look that different. Instead of having a boolean property, describing the state of our
Observable, we now directly used the
We pass this
Observable instance to the
takeUntil operator and as soon as the user clicks somewhere, our
interval-Observable will be unsubscribed from.
So all in all both code snippets look similar and behave similar, right? Well, No!
Let's have a look at the marble diagrams describing those operators because this will highlight the difference between those two operators.
takeUntil marble diagram, kindly provided by Michael Hladky
takeWhile marble diagram, kindly provided by Michael Hladky
The problem here is that
takeWhile is intended to take an incoming notification and check a specified condition on it, which might lead to an unsubscribe. The important fact is, that
takeWhile is triggered by the incoming notification and might unsubscribe afterwards. In contrast
takeUntil is triggered by the passed
takeWhile might cause several issues. So definitely, it needs a new notification to unsubscribe. Imagine having a long-living
Observable. You will need one notification more with
takeWhile than with
takeUntil. Also, this additional notification can initiate multiple processes within your
Observable. Imagine having code like this:
longLivingObservable$ .pipe( tap(() => this.startAnimation()), switchMap(val => this.makeHttpCall(val)), takeWhile(val => this.alive), ) .subscribe();
So what's the issue with this piece of code? Well, our component is already destroyed and due to the needed notification, needed before unsubscribing kicks in, we will start an animation and trigger an HTTP call. This is probably unwanted and just afterwards we will check if we want to unsubscribe from our
Observable. Besides the fact that those operations are totally superfluous, it also might break our app or pollute our state.
Additionally, if our
Observable doesn't emit an additional value, the
takeWhile will never be triggered and therefore our
Observable will never be unsubscribed. This can be considered as memory-leak, because our
Observable stays subscribed.
Now maybe one might argue: "Well, I could move the
takeWhile operator at the very beginning of the observable pipeline!"
That's true, you could do this, and you will save the unneeded operations, which is a good start, but you won't unsubscribe from inner observables. So if the
Observable returned by
makeHttpCall is a long-living
Observable, it will not unsubscribe from that, if the
takeWhile is before
switchMap in the pipe. By the way, the same goes for
takeUntil, so make sure to have the unsubscribe-operator at the very end of your pipe.
Don't get me wrong,
takeWhile is an amazing operator, but just if you actually use the incoming value to determine, whether you want to unsubscribe or not! Do not depend on "global" state, when using
For those scenarios stick to
takeUntil and use a Subject instance to trigger it.
A real-world use case for
takeWhile would be a long-polling mechanism. Imagine fetching a resource describing a process. This process can be successfully completed or otherwise ongoing. For sure you just want to continue polling while the process isn't completed yet. The code for such a scenario could look like this.
longPolling$.pipe(takeWhile(process => process.completed)).subscribe(() => handleNotCompleted());
For such a scenario, where we use the incoming will, to determine wether we want to stay subscribed or not,
takeWhile is ideal! If we have an external trigger, stick with
- use takeWhile when the incoming value make you want to unsubscribe
- use takeUntil when there is an outer event determine that you want to unsubscribe
- use both of them as the last operator in your
I'm really thankful for all the amazing people helped me writing this blog posts.
This goes out to: