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
- 0. Important Firestore Caveats
- 1. Create a Generic Service to Extend Firestore
- 2. Get Observables with a String
- 3. CRUD Operations with Server Timestamps
- 4. Upsert (Update or Create) Method
- 5. Get Collections with Document Ids Included
- 6. Inspect Data Easily
- 7. Using The Geopoint Datatype
- 8. Handle the Document Reference type
- 9. Make Atomic Writes
- 10. Delete Entire Collections
- Full Service Code
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
Advanced Firestore Usage Guide With Angular
Episode 56 written by Jeff DelaneyHealth Check: This lesson was last reviewed on and tested with these packages:
- Angular v6.1
- RxJS v6.2
- Firebase v5.3
Update Notes: Updated service for changes to Firestore and changed RxJS to pipeable operators.
Find an issue? Let's fix it
The following lesson provides a variety of tips and snippets that make AngularFire2 and Firestore much easier to use. The goal is to provide you with a global service that can simplify your codebase and solve common challenges faced with Angular Firebase development.
0. Important Firestore Caveats
- You cannot run
update
on a document reference that does not exist (unlike RTDB) - Collections are not ordered by document ID (unlike RTDB)
- You cannot save nested arrays (unlike RTDB)
- When you delete a document, its nested collections are NOT deleted (unlike RTDB)
1. Create a Generic Service to Extend Firestore
Benefit: Customize AngularFire to behave the way you want it.
You can extend AngularFirestore
database service by wrapping it with your own Angular service. This allows you to inject your own custom firestore features into any component.
ng g service firestore |
The goal of this service is to (1) increase readability, (2) reduce code, and (3) extend functionality.
import { Injectable } from '@angular/core'; |
2. Get Observables with a String
In AngularFire v5, the reference to an object is decoupled from the Observable data. That can be useful, but also requires more code. Sometimes I just want my Observable
data in a concise readable format.
I created a predicate type that accepts either a string
or an AngularFire(Collection | Document)
. This gives you the flexibility to pass these helper methods a string or firebase reference. In other words, you don’t need to explicitly define a reference every time you want an Observable.
The methods in this section are reused throughout this lesson, so do not skip this part.
Benefit: Return observables with a firestore reference or just a single string, making code more concise and readable.
// *** Usage |
// *** Usage |
3. CRUD Operations with Server Timestamps
Firestore does not automatically order data, so you need to have at least one property to order by. To address this concern, I have extended the write operators in AngularFire to automatically maintain a createdAt
and updatedAt
timestamp.
When working with a frontend JavaScript framework like Angular, the only way to keep a consistent timestamp is via a back-end server. We can use do this in Firestore with the FieldValue.serverTimestamp()
. I recommend using a typescript getter to make this operation readable.
Benefit: Always have something to orderBy
with server-side consistency in your collections.
// *** Usage |
4. Upsert (Update or Create) Method
My custom upsert()
method will first check if doc exists. If YES it will update non-destructively. If NO it will set a new document.
Note: You can also use db.set(data, { merge: true })
to achieve similar results. However, this makes it difficult to automatically manage the timestamps in the previous step.
Benefit: Never worry about document does not exist errors.
// *** Usage |
5. Get Collections with Document Ids Included
A common task is to query a collection, then use the ID to query a single document from that collection. Including the document ids in the array returned by AngularFire2 results in some pretty ugly code, so it’s nice to have this wrapped in a simple helper method. This is essentially just valueChanges()
+ document IDs.
Benefit: Return document keys with one line of code
// *** Usage |
6. Inspect Data Easily
Sometimes I just want to see what I’m working with. It seems silly to re-import RxJS operators and subscribe to data every time I want to do this. I also wrapped the operation with a timer so you can check the latency for a given query.
Benefit: Single line of code to console log the snapshot and time its latency.
// *** Usage |
7. Using The Geopoint Datatype
If you’re building a map-based app, you’re going to want to make use of the GeoPoint
class. It will give you consistent formatting and lat/lng validation for location data. Here’s how we can make one in AngularFire.
// *** Usage |
8. Handle the Document Reference type
A Firestore document can embed references to other Firestore documents - an awesome little feature, but not so easy to take advantage of with AngularFire2.
Here’s how you would associate an item doc with user doc in Angular.
const itemDoc = this.db.doc('items/xyz') |
I am going to leverage the helper methods from our service in this section. Refer back to section 2 to review how the doc$
helper method works. Here’s how to get the a user Observable if it is referenced on a note document.
this.user = this.doc$('items/xyz').switchMap(doc => { |
Alternatively, we can create a pipe for use in the HTML. The pipe takes the raw firestore document reference and converts it into an Observable.
ng g pipe doc |
import { Pipe, PipeTransform } from '@angular/core'; |
Here’s how it looks in the HTML. (The note
is a document that references at user
document.)
<div *ngIf="noteDoc | async as note"> |
9. Make Atomic Writes
An atomic write operation occurs when all operations succeed/fail together. In a SQL database, atomic writes are baked in by default. Firestore and Document databases in general require atomic writes to be structured in a specific way.
In this example, we use the Firestore SDK directly to make the updates. You perform operations on the batch
instance, then run batch.commit()
to run everything together.
atomic() { |
10. Delete Entire Collections
When you delete a document in Firestore, it’s nested sub-collections are NOT deleted along with it. Furthermore, AngularFire does not have a built-in method for deleting collections, so let’s modify the one from the main API documentation.
This section has been made into an entire video lesson about deleting Firestore collections.
Full Service Code
Copy and paste the full code to start using these helper methods in your own project.
import { Injectable } from '@angular/core'; |
import { Injectable } from '@angular/core'; |