RxJS & Firebase 101

David East
Firebase Developers
5 min readAug 5, 2020
A jar of jelly with RxJS written on it, a jar of peanut better with Firebase on it. Then the title RxJS & Firebase 101.
RxJS & Firebase go together like peanut butter and Jelly.

RxJS and Firebase are a perfect fit. RxJS simplifies highly asynchronous code and Firebase is highly asynchronous. Here at Firebase we love RxJS so much we created an official library: RxFire.

But what does using RxJS and Firebase do for you? Think about the situations where you’ve wanted to combine multiple data sources into one view model for your client. Situations like:

  1. Joining data at different locations in Firestore.
  2. Getting a user asynchronously and then getting their associated data from Firestore.
  3. Combining Firestore data with an image from Cloud Storage.

Unmanaged nested callbacks can create messy code. With RxJS you can merge and manage async data to one organized view model.

While RxJS can simplify your code, it has a bit of a learning curve. This article is going to help you get over the curve while teaching you how to use RxFire. Think of RxFire as your toolbox for turning callbacks from the Firebase SDK into Observable streams.

What’s an Observable? I’m glad you asked.

Observables

The basic unit of RxJS is the Observable. It is an amazing tool for keeping track of asynchronous events. Think of an observable as your handle over your past, present, and future values.

This code uses RxJS’s fromEvent function to create an observable from a button’s click event. Every time the button is clicked the observable will fire a click function. But why is this important? This looks like just a fancier way to call addEventListener() . Well, RxJS can do a whole lot more, like combine events.

The code above takes multiple click listeners, turns them into observables, and merges them together. Any time one of the buttons is clicked, the .subscribe() callback will be fired. RxJS gives us the simplicity of merging multiple async events into one .subscribe() callback. That’s the goal: take multiple async streams and wrangle them into one nice callback.

RxJS + Firebase = RxFire

Observables work great with a database like Firestore, which emits values as clients update documents in realtime.

Just like RxJS provides functions to create observables, RxFire provides a set of functions that creates observables from the Firebase SDK. With Firestore, you can retrieve a document or collection as an Observable by passing in a Firestore reference.

The code above does the following:

  1. Imports the Firebase, Firestore, and RxFire packages.
  2. Initializes a Firebase app to a project. Then creates a document reference for the restaurant and a collection reference for its reviews.
  3. Creates two observables of restaurants and reviews. Then kicks off the streams by calling .subscribe().

A note on being cool 😎: You may have noticed the $ notation on restaurants$ and reviews$. This is a common pattern, called Finnish notation, used to note that the variable holds an observable. You don’t need to do it. But I know that I sure feel cool when I do.

Whenever a restaurant or review changes you’ll receive an update in their respective .subscribe() callback. There are now two streams of async events. We can combine them together with RxJS’s operators. This is where the power of RxJS shines.

Fundamental Operators

Let’s say you had an in memory array of reviews. The ratings of the restaurant come back as a floating point number.

To round the ratings of restaurant reviews to a nice flat number, you could use the array method .map().

The code above simply returns a new object with a rounded rating. RxJS’s operators are similar to array methods. They are functions that are applied to the asynchronous events as they are emitted from an observable.

You could do the same thing with RxJS, and every time a new array emits the operator is applied.

Here’s what’s going on.

  1. Operators are imported through the rxjs/operators sub-package.
  2. The .pipe() method is the funnel for all operators applied on the reviews$ observable. This setup allows you to only include the operators you need in your build.
  3. The map() operator provides a callback that gives you access to the values within the Observable. In this case it is the array of reviews. Here you use the array .map() to round the rating and create a new object.

How is this any different from a regular array? Remember the purpose of an Observable. It updates you when new values arrive. Operators allow us to control what happens after each emission. Regular arrays are great for iteration but not for observation. RxJS gives us both.

RxJS provides operators like map(), filter(), and reduce(), but it doesn’t stop there. This is an entire library of operators that allow you to combine multiple observables together. This combination is especially useful in the Firebase world. Often in Firebase development you need to combine two asynchronous values, such as a Firestore document and a collection, or a document with an image in Cloud Storage. Without a library like RxJS, this usually involves a lot of nested callbacks. With RxJS, it’s actually quite simple.

Flattening Operators

Let’s say you needed to combine the restaurant document with its collection of reviews. Currently they are two different observables with their own subscribe blocks. Using the withLatestFrom() you combine them into one .subscribe() callback.

The code above does the following:

  1. Imports the withLatestFrom() operator from rxjs/operators.
  2. Uses the withLatestFrom() operator to combine two data streams into one. In the .subscribe() callback will emit both the restaurant data and its reviews.

Look at that! You can join data with just three short lines of RxJS code. This is really helpful for gathering all the denormalized data in your database into a normalized view model for the client. Currently the restaurant and its reviews are returned as an array.

[{ /* single object restaurant*/ }, [ /* array of reviews */ ] ]

But if you wanted to make reviews and property of restaurants, you would just need to use the map() operator to reshape the object.

Now in the subscribe block, you can tap into the reviews in a traditional manner: restaurant.reviews.

[{
rid: 1,
name: 'Taco Planet',
category: 'Taco',
reviews: [
{ revId: 123, text: 'Best tacos ever.' }
]
}]

Think of RxFire as a way to create View Models

This technique of combining multiple observables together is similar to creating a query in a SQL database to create the model needed for a view. Instead of dealing with multiple callbacks, create each async operation as an observable and use operators to funnel the async flow into one single .subscribe() callback.

Any time you find yourself nesting callback functions in Firebase you should think about adopting RxFire.

Resources

--

--

David East
Firebase Developers

Developer Advocate at Google. Working on the Firebases.