RxJS tapOnce operator — yet another solution

Wojciech Trawiński
JavaScript everyday
3 min readApr 24, 2020

Yesterday I was catching up on the latest articles published at Angular In Depth when I stumbled across a blog post about implementing a custom RxJS operator tapOnce. I felt like I had already seen it somewhere and it was Netanel Basal who provided a similar implementation in his article about the defer operator. Although the solutions presented in the above-mentioned blog posts work perfectly fine, I would like to come up with yet another approach 🚀 inspired by one of the latest articles by Tim Deschryver.

How they solved it

The easiest way to solve the task is to introduce a local variable which controls whether or not the side effect fires 🔥. At first you may come up with the following solution:

Unfortunately, the above implementation is incorrect 😢, since the isFirst variable is shared for all observers, hence only the first subscription will trigger the side effect:

In order to make it work as expected 👷, you need to make use of the defer operator which calls the observable factory function for every new subscription and creates a separate isFirst variable for each observer:

Now everything works as intended 😆:

However, I don’t feel quite happy 😟 with the above solution, since it’s a little bit imperative and you need to perform a logical check for each value even though the condition will not change once the first one has been emitted.

How I solved it

I would like to present you a bit more declarative way to accomplish the goal. You can summarize the problem as follows:

Before I subscribe to the source stream I would like to subscribe to a little bit modified one which is based on the input observable. The stream should perform a side effect for values emitted by the source. I’m only interested in the first such notification.

Putting it into TS code with RxJS results in the following implementation 🎆:

I make use of the concat operator in order to subscribe to the sharedSource$ once the tapped$ observable has completed which happens after emitting the first value:

Note that the source stream has to be multicasted so that the subscription to the second observable passed to the concat operator doesn’t result in a new notifications producer creation ⚠️. Without multicasting:

the result would be incorrect, since the subscription to the source$ observable would fire a new interval :

Feel free to play around with the example:

I hope you liked the post and learned something new 👍 If so, please give me some applause 👏

--

--