Becoming more reactive with RxJS flatMap and switchMap
If you’re new to RxJS, you may have experimented with creating a few observables and applying functions like map, filter, and scan. These are intuitive for most developers, since they constitute the building blocks of common functional programming tasks. But at some point, you will probably run into some more intimidating sounding methods, namely flatMap and switchMap (for the purpose of this post, I’m sticking with the RxJS 5 API, which has some different naming conventions compared to RxJS 4). I’m also going to assume some familiarity with common array methods in Javascript like filter, map, and reduce (but mostly map), and a bit of exposure to observables.
Let’s start with flatMap. The official documentation describes it like this:
“Projects each element of an observable sequence to an observable sequence and merges the resulting observable sequences or Promises or array/iterable into one observable sequence.”
While accurate, this description might initially come off as a little opaque. I usually get lost the somewhere around the thirteenth use of the phrase “observable sequence.” Even if for some reason it makes perfect sense instantly, you might be wondering when you would want to do something like this.
Building flatMap
In order to start to understand how flatMap works, let’s refer back to what most of us already have a pretty good grasp of: arrays. That way, we can build a version of flatMap ourselves which will work on arrays. Once we’ve done that, it’s not too big of a mental leap to see how it works on observables in RxJs.
Let’s say we have an array called oddNumbers
:
const oddNumbers = [1, 3, 5]
Now how would we transform oddNumbers
into an array with the numbers 1 through 6? (and let’s try to do it in a fancy functional way, rather than a big for loop — it will help when we get to RxJS).
Try to do this yourself, then let’s compare approaches. Here’s a JS Bin if you want to play with the code as we go (encouraged).
Let’s start off with map:
const oneToSix = oddNumbers.map(x => [x, x + 1])
console.log(oneToSix) // -> [[ 1, 2], [3, 4], [5, 6]]
We’re close, but we ended up with a nested array. We really want one array. So let’s concatenate the results here with a function called flatten.
function flatten (arr) {
return arr.reduce((flatArr, subArray) => flatArr.concat(subArray), [])
}
(This is not the best implementation of flatten, and not really the point of this post, so don’t worry if it’s unclear. Just know that it will take [[1, 2], [3, 4], [5, 6]]
and return [1, 2, 3, 4, 5, 6]
)
Now, we can get oneToSix
by combining our map and flatten:
const oneToSix = flatten(oddNumbers.map(x => [x, x + 1]))
console.log(oneToSix) // -> [1, 2, 3, 4, 5, 6]
We’re getting close, as you can probably tell by the words “flatten” and “map” right next to each other. In fact, that’s all flatMap is: the combination of mapping over an iterable, with the additional step of flattening the result. In creating oneToSix
, we actually already used flatMap — the implementation was just split up into two different functions. Imagine if we needed to continuously remember to wrap our results in a call to flatten. That would end up getting annoying — so instead, let’s see if we can combine these operations into a single function.
function flatMap (arr, fn) {
return arr.reduce((flatArr, subArray) => flatArr.concat(fn(subArray)), [])
}
console.log(flatMap([1,3,5], x => [x, x + 1]))
// -> [1, 2, 3, 4, 5, 6]
flatMap now will iterate over our input array, take each subarray, apply our mapping function to it, and then conveniently concatenate the result into our output array so that we don’t end up with nested arrays. That’s all flatMap does. If our mapping function turns each input into a new array, flatMap will help stitch everything together so that our function can take an array one level deep and returns an array one level deep. You might wonder why this is useful beyond the garbage example of “oneToSix” I presented you with. (If you are ever asked to implement oneToSix
using oddNumbers
, though, you will know who to thank). The good news is that although flatMap gets a little more complex with observables, I think its usefulness starts to shine more.
Extending to observables
Now that we’ve built flatMap, let’s see how it works with observables. Remember: with arrays, flatMap applied a mapping function to each element of the array, and then flattened the result into one big array (which was only one level deep — no nesting).
Suppose we want to use observables to make an http request every second and log the result. How would you do this using RxJS? Go ahead and give it a shot, I’ll be over here talking about flatMap. Here’s a link to JS Bin for the code below.
First, let’s make the observable for each second:
const second$ = Observable.interval(1000)
Now, let’s make an observable to represent our http request:
const response$ = Rx.Observable
.fromPromise(
fetch(‘https://www.googleapis.com/books/v1/volumes?q=isbn:0747532699').then(response => response.json())
)
We have a stream of seconds and the http request in observable form. Now we just need to map each tick of the seconds observable so that it makes the http request. We can combine them like this:
second$
.map(x => response$)
.subscribe(data => console.log(‘data is’, data))
// -> <huge mess>
There’s a problem here. What data type does our mapping function return? That’s right — an observable. Sort of similar to what we saw in oneToSix
, our result is now nested: it’s an observable of observables. Every tick of the seconds observable is mapped into another observable containing the http request. This point is pretty important to making everything click, so don’t be afraid to spend some more time mulling it over.
So how do we fix this? If only there was a way to take all of the values that came through each new response$
observable, and keep flattening them into one single observable, which we could then subscribe to…oh, hello flatMap. We can easily solve our issue now:
second$
.flatMap(x => response$)
.subscribe(data => console.log(‘data is’, data))
// -> <JSON response data>
And now we’re good. Each tick in the second$
observable will get mapped into a response$
observable. Each response$
observable will emit the data we want. flatMap will take all of the values from each new response$
observable, and stitch them together with those of the next response$
observable. It will keep doing that over and over so that we don’t need to worry about logging an observable instead of the data it contains — we’ll now get all of the data we care about inside a single clean observable.
Switching to switchMap
So that’s flatMap. We’re not done yet though — we still have to explore the cooler sounding switchMap, which can do some awesome things with observables.
To begin, let’s think back to arrays for a second. One crucial dimension was absent when we were working with them: time. But time is important with observables, and it’s part of the reason we need switchMap. Let’s illustrate this with an example. I know we were making some great progress in the practicality of our examples — making an http request and everything, but unfortunately we’re going to regress briefly (a real world example will follow, though).
One day when I was in kindergarten my teacher told us to sing Row, Row, Row Your Boat in a round. I still don’t understand what the point of that exercise was, other than to demonstrate what it would sound like if a bunch of insane people decided to sing the same song to themselves but all start at different times. Maybe it would have worked better in college, I don’t know. What my teacher could have instead done was use this example to demonstrate what switchMap does. I’ll just have to do it instead.
Let’s write a program that will simulate what it’s like to listen to Row, Row, Row Your Boat when sung in a round (except you’re not listening to it and actually just reading the lyrics).
First let’s get all of the words into an array. As usual, here is the JS bin.
const words = ‘Row row row your boat gently down the stream merrily merrily merrily merrily life is but a dream’.split(‘ ‘)const numWords = words.length
Now our first goal is to make an observable to simulate one person singing the song.
We’ll need the following:
a) an interval
b) a way to map ticks from the interval into words
const singer$ = Rx.Observable
.Interval(500) // emit a value every half second
.scan(x => x + 1) // record the count
.map(count => count % numWords) // convert into a resetting index
.map(index => words[index]) // map to the wordsinger$.subscribe(console.log)
If you test that, you’ll see it sing forever. Pretty cool stuff. But how about the “singing in a round” challenge? Well, now we need to keep creating new singer$
observables at some interval. It sounds like an observable of observables might get involved.
Now we need:
a) Another interval
b) A way to map each tick into a new singer$
c) A way to combine the values from each new singer$
into a single observable (I hope you have an idea for this one)
const round$ = Rx.Observable
.interval(4500)
.flatMap(() => singer$)round$.subscribe(console.log)
Ok, that actually does a pretty good job of encapsulating what the room sounded like that awful kindergarten day. Let’s try to tone things down a bit.
Instead of showing every single value from every new singer$
, let’s instead keep one at time. Each time a new observable is produced, we’ll throw out the previous one and never see its values again. This is what switchMap does — its name is very descriptive. It allows us to map and flatten like flatMap, but it “switches” to each new observable and forgets whatever came before it. You can swap out flatMap without changing anything else — they have the same signature.
const brokenRecord$ = Rx.Observable
.interval(4500)
.switchMap(() => singer$)brokenRecord$.subscribe(console.log)
Now we never manage to make it to my personal favorite part of the song — the part where they say “merrily” four times in a row.
This is because each time we invoke the switchMap function, we’re “switching” to the new observable and discarding the old one. Arrays don’t really have a similar concept, because they don’t arrive over time. As many have pointed out before, observables are pretty much arrays whose values arrive over time. It’s this introduction of time into the equation that makes switchMap a thing — it says “let’s apply a mapping function and flatten the result so it can be operated on as a single observable, but, just emit values from the most recent result.”
Why this is useful
Thanks for bearing with me during that last example. This last one will be more useful, and relies heavily on switchMap.
I first saw how useful these methods were when I was trying to create a pauseable observable. I needed my observable to emit values until a specific event occurred in my app, then temporarily pause the observable until receiving a different event. RxJS previously included a pauseable observable operator, but it was removed in version 5 since it can be easily recreated through our friend switchMap. Let’s see how that’s done:
Creating a pauseable observable
const normalObservable$ = // any stream of data you want to pauseconst shouldObservableBePaused$ = // this is where your pausing logic goes — it should emit boolean values describing whether or not our data should be pausedconst pauseableObservable$ = shouldObservableBePaused$
.switchMap((pause) => pause ? Rx.Observable.never() : normalObservable$)
Here’s what’s going on: we have an on observable called normalObservable$
which emits some data. Then, we have another observable called shouldObservableBePaused$
, which we’ll imagine emits boolean values. (As a side note, the normalObservable$
here is assumed to be a hot observable — I won’t get into the details of hot vs. cold observables here, but Ben Lesh has a good post on it).
switchMap brings everything together. We take shouldObservableBePaused$
, and call switchMap to return a new observable. What does that observable do? switchMap will take each boolean value from shouldObservableBePaused$
and transform it into a new observable. That observable is either a stream containing our data, or a silent observable. Each time a new boolean arrives, pauseableObservable$
potentially switches between our data and the silent observable.
If we had used flatMap, we’d still see old values from normalObservable$
if it tried to emit something when it should have been paused. That’s because flatMap doesn’t discard the old observables like switchMap does. And in case you’ve forgotten, the reason we need flatMap and switchMap at all for this vs. the standard “map” here is because we’re creating an “observable of observables” —shouldObservableBePaused$
is emitting new observables, so we need to flatten them in order to operate on them as a single observable.
Hopefully this illustrates how flatMap and switchMap can be used to start creating some more complex observable logic. I hope the diagram from the Rx docs included at the beginning of this article is slightly clearer now.
Often when I’m building something with observables and get stuck, there’s a solution involving one of these two methods (of course it may not always be the right one). It’s definitely a fundamental tool in working with RxJS.