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
Build a Group Chat With Firestore
Episode 144 written by Jeff DelaneyHealth Check: This lesson was last reviewed on and tested with these packages:
- Angular v6
- RxJS v6.3
- @angular/fire v5
Find an issue? Let's fix it
Source code for Build a Group Chat With Firestore on Github
One of the most compelling reasons to choose Firebase as your backend is to meet the demands of complex realtime features, like group chat. Managing state between multiple clients in realtime is a major undertaking, but with Firebase it becomes almost trivial. The following lesson will teach you how to build a simple group chat app with Cloud Firestore.
This lesson is accompanied by a real demo! Give Firestore Mega Chat a whirl.
Data Modeling Considerations
The ideal data model for a chat application depends on several factors. Here are a few topics to think about…
- Group chat versus 1-to-1 chat.
- Privacy and user authorization.
- Queries and full text search.
- 1Mb document size and 1-write-per-second limits.
NoSQL databases are flexible. If you’re new to this paradigm, I highly recommend reviewing the Firestore NoSQL data modeling lesson.
Messages Collection Approach
The most flexible model is multiple collections (or subcollections) for chat sessions and messages. This approach allows you to query messages serverside, but requires a unique read for every message. This data model is one you should consider, but not the approach we are taking for this demo.
Embedded Document Approach
Our app will model embed all messages on a single document, allowing us to grab hundreds of messages with a single read operation. This approach is fast and simple, but the drawback is that Firestore limits you 1MB per document, or conservatively, around 1K chat messages. At the end of the lesson, we’ll setup a cloud function to automatically manage the document size by archiving older messages.
Another benefit of the embedded approach is that Firestore recently added an arrayUnion
helper that enforces uniqueness and makes adding items to the array idempotent.
{ |
Notice how we only save the user’s UID on the chat message. Later this lesson, I will provide a joinUsers
method to combine the user data, like displayName and photoURL, to each message in the UI.
Firestore also has a limit of 1-write-per-second, but you can burst past it for short periods. This limit is only a concern if you have consistent high volume writes on a single doc. You can learn more in this github issue.
AngularFire Chat App
Now that we have a data model in place, let’s build out the UI with Angular and @angular/fire.
ng new firechat --routing |
Add a route for the chat component in app.routing.module
:
const routes: Routes = [ |
Next, follow the install instructions for Firebase and AngularFire
User Authentication Service
You need to have a user auth system in place that saves a user’s profile data in firestore. The auth service below will do the trick and for a full explanation you can watch Episode 55 - Google OAuth Custom Firestore Profile.
import { Injectable } from '@angular/core'; |
Chat Service
The chat service gives us a single place to retrieve and write data from Firestore. Here’s a breakdown of what each method does.
- get retrieves the chat document as an Observable.
- create writes a new chat document
- sendMessage uses the Firestore
arrayUnion
method append a new chat message to document.
import { Injectable } from '@angular/core'; |
Joining User Profile Data to Chat Messages
The code below is the the most advanced part of this lesson. It grabs the unique IDs from the chat messages array, then joins the user profile data to each message and keeps the entire payload synced in realtime. I highly recommend also watching the Advanced Firestore Joins lesson if you get lost in this section.
joinUsers(chat$: Observable<any>) { |
Chat Component
Most of the complex data management code lives in the chat service - now we need to make use of it in a component.
import { Component, OnInit } from '@angular/core'; |
In the HTML we can unwrap the Observable with the async
and bind its data to the template.
<ng-container *ngIf="chat$ | async as chat"> |
Archiving Messages
The AngularFirebase Slack Channel has generated over 150,000 messages in the last 12 months, so it’s safe to assume our Firestore chat documents will need more than 1Mb of space. In addition, we should keep the main chat document relatively small to also minimize data load times.
1Mb is actually quite large. There are 1 million bytes in a Megabyte and let’s assume a worst case of 5 bytes per character in each message. That gives us room for well over 1K messages at 100 characters each.
Manage Document Size with a Cloud Function
So here’s the plan… We will trigger a Cloud Function on every document write. When a chat’s messages array exceeds 100 or the JSON stringified value is greater than 10K characters, we will delete the oldest messages. This means we never fill more that 5% of the document’s capacity and we should have access to the last 100 or so messages.
firebase init functions |
import * as functions from "firebase-functions"; |
The End
A fully-featured chat app has a ton of moving parts, but Firebase takes care of the most challenging development hurdles, like state management, realtime data syncing, user auth, and scaling. The next step is to think about adding additional features, like user access control, file uploads, push notifications, and more.