Angular Master Class in Freiburg
Join our upcoming public training in Germany!Get a ticket →
This is another post in a series of articles to discover the magic of different Rx operators. In our last article, Exploring Rx Operators: map we learned how we can map the notifications of Observables to create other more meaningful Observables.
Today we like to move our attention to another very important and also related operator, namely
flatMap. In the same way that the
map operator is closely related to the
map function that we know from Arrays,
flatMap should sound familiar to many people who worked with collections in a functional programming kind of way. In fact, the similarity is so strong that it makes sense to first move our attention to a collection based example.
TABLE OF CONTENTS
flatMap for collections
Consider the following collection of invoices where each invoice has a property
positions holding the individual items of the invoice. That’s a pretty common (yet simplified) structure of pretty much every e-commerce system out there.
What if we are interested in a collection of all positions? Of course we could simply loop through all invoices and push the positions into a new shared collection.
But that’s very imperative and focuses a lot on the actual implementation. What we rather like to have is a declarative solution that focuses on what we want to achieve. Coming from a collection of invoices, we want to retrieve a collection of positions.
In the functional programming world there’s a
flatMap function for exactly this use case.
flatMap function yet. However, we can reach for the popular lodash library and rewrite the code to use
flatMap with that.
We just replaced four lines of code with a single one that is far easier to follow. This declarative solution doesn’t move our intention to the actual implementation. We don’t even know whether it’s using a for loop behind the scenes or not. Once you become familar with the vocabulary the code becomes much easier to understand then it’s imperative counterpart. Pretty neat, no?
flatMap does is, it goes through each invoice in our collection and applies our function which maps to the array of positions. It then flattens all the positions into a single collection, hence the name
flatMap. Just to make it very clear: A simple
map isn’t suitable here because it would leave us with an array of arrays which is not quite what we want.
By now you may be wondering what this has to do with
flatMap from the Rx library. The good news is, if you’ve understand the example above, understanding
flatMap for Observables isn’t a far stretch. In fact, it’s very similar.
flatMap for Observables
Remember that Observables are very much like collections with the difference that items are pushed to us as they arrive instead of being pulled out. In our example above we had a collection of invoices where each invoice had a collection of positions. Can we have an Observable of invoices where each invoice has an Observable of positions? Well, we could make that up but it would suggest that we have invoices where the positions may arrive asynchronously.
A better suited example may be an Observable of tweets where each tweet has an Observable of like actions. A
LikeAction is a simple enum with two variants
We can assign the values
-1 to the variants so that we can easily accumulate a total count of them later.
Tweet is a simple class with a
text property of type
string and a
likes property of type
Don’t get confused by the use of
Subject here. A subject is an
Observable that one can not only subscribe to but also raise notifications on which is exactly what we need to mock the actions.
Now that we have our basic models we can create an
Observable<Tweet> and save a reference to it. Again, we’re using
Subject for the purpose of mocking out our notifications.
Tweets are pushed through the
Observable<Tweet> as they arrive. Whenever someone likes or unlikes a tweet the
Observable<LikeAction> on that tweet pushes a new notification.
To make a tweet we have to create an instance of
Tweet and pass it to
next on our tweets
Adding or removing likes is as simple as calling
likes.next(val) with either
Now that we have set up the ground work let’s come to the interesting part. We like to be able to keep track of all likes/unlikes so that we can show the total number of likes of all tweets.
Remember the imperative for loop that we wrote in the previous example to collect all positions manually? We could kind of do the same here to calculate the total number of likes manually.
Special tip: Don’t get confused, the fact that we can accumulate our
LikeActionsis simply because we assigned
But just as we used
flatMap to get one single collection of positions of all the invoices we can use
flatMap here to get one single
Observable for all like actions of all the tweets.
Special tip: This is a perfect use case for the
scanoperator which can help us to get rid of the manual book-keeping in the
likeCountvariable. But let’s not get ahead of ourselves.
So what does the above code do? For each tweet that gets pushed to us
flatMap maps to the
Observable<LikeAction> on the
likes property. It subscribes to these Observables and flattens the notifications into a single
Observable<LikeAction> that we can then subscribe to.
The full working code of our example looks like this.
When we run the code we see the following output.
Observables are really powerful. They allow us to to compose asynchronous tasks in a functional reactive way. Because they share so many similarities with collections we can apply a lot of our knowledge from popular libraries such as lodash.
Get updates on new articles and trainings.
Join over 1400 other developers who get our content first.
Three things you didn't know about the AsyncPipe
This article explains three lesser known features of the AsyncPipe that help us to write better async code.
Cold vs Hot Observables
In this article we are going to demystify what the term hot vs cold means when it comes to Observables....
Exploring Rx Operators: map
This is the first article that is part of a new series where we take a look at different operators...
Taking advantage of Observables in Angular 2 - Part 2
This is a follow up article that demonstrates how Observables can influence our API design.
Taking advantage of Observables in Angular
Since version 2.x Angular favors Observables over Promises when it comes to async. In this article we explore some practical...
Using Zones in Angular for better performance
In this article we'll take a look at how to use Zone APIs to improve our app's performance!