RxJS, where is the If-Else Operator?

Nathaniel Kessler
Rangle.io
Published in
4 min readApr 30, 2020

if/else statements are a staple for handling conditional logic. It’s natural for most developers to reach for the if/else statement when a decision needs to be made in code. However, in the reactive programming paradigm (e.g. with RxJS) this conditional statement is mysteriously unavailable. How can you code without it?

This is a re-post from February 26, 2018. The code snippets on this have been updated to RxJS > 5.5 when “pipeable” operators where introduced.

TL;DR

Break up each branch of an if block into its own bite sized stream and end by tying each branch back together with a merge operator.

It’s tough to appreciate with a ‘Hello world’ example, but the concept of streams are powerful and should speak to the declarative programmers out there.

Let’s go through a more thorough example where we need to write an RX stream. For this example, let’s say we’re writing a transit app. The app calls for a new feature that checks if the next street car is pet friendly. I’ll start with an example in RxJS that walks a thin line between reactive functional programming and imperative programming (the kind full of if/else statements). Then we’ll clean it up using the “more streams” trick mentioned above and find out how to do away with if/else statements.

New feature: Is this train pet friendly?

As a user of the app, I need to know if the next train is pet friendly. When I click the “Get Next Train” button, a message with details including pet info should be displayed.

Train Api Service

Leveraging this train API service, let’s write out a stream that subscribes to button click events and returns details about the next train. The details need to include whether or not the next train is pet friendly.

Here’s what’s happening:

  • each click is emitted in a stream
  • each click is mapped to train details of the next train (map(trainApiService.getNextTrain))
  • the next .map() goes into a bit of conditional logic to check if the next train is pet friendly with if (trainApiService.isPetFriendly(train.id)). Depending on the condition, a message is returned with the necessary pet information
  • the tap(ui.showTrainDetails) takes the previous message and updates the UI in the app

What’s interesting here is that this stream has a branching condition in the middle:

if (trainApiService.isPetFriendly(train.id))

But, regardless of the conditional outcome, it ultimately ends up displaying a message:

tap(ui.showTrainDetails)

What if instead of only displaying a final message, the outcome of the condition would also have to determine if a “pet friendly icon” was displayed in the UI: ui.showPetIcon(). This means the stream of clicks would have to end with two different outcomes. That might look something like this:

At this point it might be tough to follow what’s going on. We are using a conditional statement to build up the train details message and another conditional statement inside the final tap() operator to update the UI with the pet icon. There's also a few new variables we have to keep track of in order to achieve the branching logic:

  • let messageDetials;
  • const isPetFriendly = trainApiService.isPetFriendly(train.id);

That’s a lot to keep in mind when writing out a stream like this. The big takeaway here is that you shouldn’t have to. Rx streams can be very readable if you approach them differently.

More streams

It’s simple to cut out the if statement when you approach the problem in smaller pieces. Consider this if statement:

The something() on line 2 wants to be called, but it's gated off by the if (isSomething) on line 1. In other words, if statements (line 1) act a lot like filters. The RxJS version is just that:

But the topic here is if/else, not just if.

We can’t branch to the else portion of this condition with the filter operator from before, but that’s okay. Instead, you can break the statement into multiple streams (one for each branch of the condition) and then compose them together with a merge operator.

if statements, however, don't end there. There's also the else if statement.

This essentially translates into more branches. By following that same approach as before in the RxJS way, we can break each branch into its own stream and merge them all together at the end.

The beauty here is that the final stream is just a composition of the things the developer is after. Compared to the imperative version (if/else), this one reads like a natural conversation. You don’t need to bother considering the other streams. You can just zero in on what the final stream is composed of and assume the goal based on the verbiage.

“I want somethings$, betterThings$ and defaultThings$

The imperative version, on the other hand, will cost more mental overhead to read through as we saw before in our transit app. At each branch of the if statements you’ll have to mentally process the condition line:

if (something !== somethingElse) {

Before you can read into the action:

doSomething()

Merge

Now that we’re armed with this pattern, refactoring out the conditional statements from our transit app should be a snap. Let’s convert each branch of our conditions and then compose them together.

We’ll create a source stream of clicks that map to “next-trains”. Then we’ll build a branch for pet friendly trains and one for non pet friendly trains. Lastly, we’ll merge them together and call it a day.

Compared to our early attempt at the pet friendly feature, this one is more readable. We’ve built out the branches into single independent streams that are easy to digest. And we used those branch streams to compose our final output:

“I want petFriendlyTrains$ and nonPetFriendlyTrains$.”

--

--