Using RxMarbles to find the perfect Observable operator
I recently had a minor success in the world of Observables. Observables and RxJS, while incredibly powerful, can be intimidating and difficult to wrap your head around. In this blog post I’m going to describe a technique that I found useful when trying to work through an Observable head-scratcher.
In case you’re unfamiliar, Observables are a powerful construct for writing asynchronous code that deals with streams of data. That’s a massive oversimplification but there are many guides out there which introduce observables so I won’t attempt to re-document the wheel here. If you’re not familiar with them I definitely recommend checking them out.
The Bug
One of the main projects Jon and I work on has a token system. When users answer a question correctly, they earn a token and we play a congratulatory sound. We found a strange bug where whenever a user would update their profile, it would play the coin sound as though they had just answered a question correctly. Now, even though we might not mind congratulating our users for modifying their settings, it was definitely not the intended behavior!
To understand this bug, let’s take a look at the code that handles the playing of the sound:
combineLatest( user, // Observable which represents the user lastTokenEarned), // Observable which emits every time the user earns a token ).subscribe( ([user, token]) => { if (token && user && user.settings.soundOn) { this.coinSound.play(); } });
We’re using the combineLatest operator with the user and the last token earned. Whenever a new token is emitted, we also have the latest user so that we can check their settings to see if they have sound enabled. Fairly straight forward.
In order to better understand the issue, let’s take a look at the RxMarbles diagram for the combineLatest operator. RxMarbles is a super useful tool which allows you to visually see the behavior of a particular operator or constructor given a particular input.
Here is the diagram for combineLatest:
In the diagram above, the top two lines represent the input Observables and the bottom line represents the output of the resulting Observable. As you probably already know, the code in the subscribe() callback is called every time the Observable emits a value. And if we look at the diagram above, the resulting observable is emitting a value regardless of which observable is emitting a new value. In the case of my token sound code, this meant that even though it was correctly playing a sound whenever a new token was emitted, it was also being called every time a new user was emitted, for example when the user updated their profile.
More generally, the problem with my mental framework when writing the code is that I was implicitly assuming that the user is a static and unchanging value which obviously it isn’t otherwise we wouldn’t be using an Observable to represent it! This serves as an important reminder that when working with Observables, it’s important to consider the circumstances which will cause your Observable to emit a new value.
Okay so now to the crux of the blog post, what magical strategy do we use to fix this?
The fix
So combineLatest() isn’t doing what we need it to. What I found really useful in this case was to diagram out the hypothetical RxMarbles diagram of the hypothetical operator that I needed:
Notice the key behavior of this operator that it only emits a value on the output Observable when the bottom Observable emits a value but it still has access to the latest value from the other Observable. Going back to the sound case, this will allow us to trigger the token sound code when the token Observable emits and just use the user Observable for its value.
So how do we find our mystery operator? Does such an operator even exist??
withLatestFrom
To be perfectly honest with you, I don’t have some magical system that accepts an rxmarble diagram and spits out the name of the operator (although that does sound like a pretty cool project!) My only advice is to peruse the operators on RxMarbles and look for an operator name that seems promising, and check the corresponding diagram. Luckily in my case, that worked like a charm.
My knight in shining armor was the withLatestFrom() operator. This operator allows you to access the latest value from an input observable without triggering the Observable on new emissions. Exactly what we’re looking for.
Let’s take a look at the revised version of the code:
lastTokenEarned.pipe(, withLatestFrom(user)), ).subscribe(([token, user]) => { if (token && user && user.settings.soundOn) { this.coinSound.play(); } });
Ah relief, now our user settings page is blissfully quiet!
Conclusion
To recap:
Keep in mind the circumstances under which your Observable chain will emit a value
Use the visual formula that rxmarbles uses to clearly diagram the desired behavior of your Observable
Use your improved understanding of your desired behavior to find an operator (or combination of operators) to get the job done.
Last but not least, withLatestFrom is a handy operator if you need a value from an Observable, but don’t want to trigger your Observable from new values from it.
As I mentioned before Observables are a powerful and sometimes intimidating tool to work with. But I think you’ll find after working with them that they’re actually quite fun and satisfying to work with. There’s nothing quite like putting the final close parenthesis in place that makes all the red squigglies go away.