You have unlimited access as a PRO member
You are receiving a free preview of 3 lessons
Your free preview as expired - please upgrade to PRO
Contents
Recent Posts
- Object Oriented Programming With TypeScript
- Angular Elements Advanced Techniques
- TypeScript - the Basics
- The Real State of JavaScript 2018
- Cloud Scheduler for Firebase Functions
- Testing Firestore Security Rules With the Emulator
- How to Use Git and Github
- Infinite Virtual Scroll With the Angular CDK
- Build a Group Chat With Firestore
- Async Await Pro Tips
Join Collections in Firestore
Episode 134 written by Jeff DelaneyHealth Check: This lesson was last reviewed on and tested with these packages:
- Angular v6
- RxJS v6.2
- AngularFire2 v5.0.0-rc.12
Find an issue? Let's fix it
Source code available to pro members. Learn more
Full source code for Join Collections in Firestore on Github
How do I perform a SQL JOIN in Firestore? - it’s a difficult question almost all developers will come across. The simple answer for ALL NoSQL databases is that it’s just not possible in an apples-to-apples way. We can’t perform this operation server-side, however, we can get clever with custom RxJS operators to solve similar problems - plus gain the added benefit of maintaining realtime listeners on all data.
Our operators require AngularFire and will add some RxJS magic to its existing Observables to tackle the challenge of joining Firestore documents and collections together easily in Firestore. The code in this lesson is advanced - if you get stuck checkout these related lessons:
Document to Document JOIN
Joining documents together is slightly easier than collections. A common data model in NoSQL is to save a field with a document ID that points to a related document. We could just perform separate document reads one-by-one, but that would be cumbersome. Let’s build an operator that can handle this process seamlessly as a single Observable.
This lesson uses strings to keep track of relational data, but you might also use a Firestore DocumentReference
when dealing with large complex paths.
Usage
Consider the following data model. We have three unique docs in separate collections. The user doc makes a reference to a specific pet and car ID - this can be a has one or belongs to relationship.
+users |
The first argument is the AngularFirestore
instance. Operators are pure functions, so they we have to pass dependencies as arguments. The second arg is an object where each key is the field with the related doc ID and the value is the collection containing this doc. The result is a joined object that joins the related document data as a object on each key.
const user$ = afs.document('users/jeff').valueChanges() |
docJoin Code
The code for this custom operator can be broken down into three major steps.
- Retrieve the data from the parent document, save it to an internal variable so it can be combined with the joins.
- switchMap to the doc reads of the relational data and combine them with combineLatest. This will wait for all reads to finish before any data is emitted.
- Map the joins to the parent document as a single object Observable.
import { AngularFirestore } from 'angularfire2/firestore'; |
import { AngularFirestore } from 'angularfire2/firestore'; |
Collection Join (Left Join SQL)
One of most useful types of joins in a SQL database is a LEFT JOIN, which gives us all records from the first table, then any matching records from another table based on a shared key.
We can achieve a similar style query in Firestore when documents from two collections share a common key-value pair, similar conceptually to a primary and foreign key in SQL. In this example, it will replace the keys on the left collection with the query data from the right collection.
Usage
Let’s say we have a database structure that looks something like this below.+users
docId=jeff {
userId: 'jeff'
...data
}
+orders
docId=a {
orderNo: 'A'
userId: 'jeff'
}
docId=b {
orderNo: 'B'
userId: 'jeff'
}
Our goal is to query the users collection, then query each orders collection for every doc, for instance: orders.where('userId', '==', userId)
.
The operator has three required arguments and an optional limit leftJoin(afs, joinKey, joinCollection, limit=100)
.
const users$ = afs.collection('users').valueChanges() |
leftJoin Code
The code for a collection join is slightly more complex, but overall pattern is identical to the previous operator. Let’s break them down again:
- Call switchMap on the initial query.
- Loop over documents in that query, setting up a secondary query based on the common key-value pair between the documents. Perform all join queries together with combineLatest.
- Map them join queries to documents in the original query.
I’ve also added custom logging to this operator so we know the total documents in the joined query. In the console, it will log something like Queried 25, Joined 100, so you know that 125 reads were executed.
import { AngularFirestore } from 'angularfire2/firestore'; |
import { AngularFirestore } from 'angularfire2/firestore'; |
The End
We built two unique RxJS operators that perform joins in Firestore and solve similar problems to those in SQL databases. Keep in mind, there is no one right way to do this - consider it a starting point to create abstractions around your own business logic.