RxJS tapOnce operator — yet another solution
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 👏