Learn how to build a group chat app with Firestore. It's alive... let's chat on the live demo 🎪👉 https://firestore-megachat.firebaseapp.com/

Full Lesson https://angularfirebase.com/lessons/build-group-chat-with-firestore/
2024-02-04T07:10:52.593Z
nova-2
2-general-nova
2024-01-09.29447
30089e05-99d1-4376-b32e-c263170674af
327fac91-065a-42ac-857c-369233cbdab3
dfaa4af50904f4473e5283bff0f7f3173a1ccb2c30d4d079be1b2cd6e6974213
67875a7f-c9c4-48a0-aa55-5bdb8a91c34a
deprecated
Building a real time chat app used to be something that would take at least weeks of development time.
But with Firebase, it becomes almost trivial because the SDK handles all of the state management and data syncing between the client and server for you.
It's so easy, in fact, that I built an entire chat app in the last 24 hours that you can use right now.
And over the next few minutes, I'll show you lay how I built it using AngularFire and Firestore.
If you're just finding the channel, like and subscribe.
And if you leave a comment below, you'll have a chance to win either a sticker or a t shirt on the live stream later app.
Now, the first thing I wanna talk about in this video is data modeling, because there's no one size fits all for every chat feature.
The first thing every app will need is a user authentication system and then some public user profile information saved in a user's collection in Firestore.
That's pretty straightforward, but you have a lot of different options when it comes to modeling the relationship between users, chats, and individual messages.
I think the logical first approach that most people would take is to set up a collection of chats, and then for each of these chats, create a sub collection of messages.
This is a good way to go because you can save all of your Chat Metadata on one small document and then query as many messages as you need from the Sub Collection.
Action.
And as an alternative, you might make this a RootCollection, so you can query the messages across all of the different chats throughout your application.
For example, a RootCollection would allow you to query all the messages for a specific user across all of the different chats that they're participating in.
I think this approach is the most flexible, but the drawback is that you need to execute a document read for every message that you show in the UI.
Eye.
In this video, we're going to take a completely different approach by modeling each Chat as its own document and then embedding the messages for that Chat on the document.
This method is fast and simple, but there are some limitations.
1st, we can no longer query the individual messages because they're embedded on the document.
We can only sort them client side.
And the second thing is that documents are limited to 1 megabyte of data, which is conservatively somewhere between 250 and a 1000 messages per document.
To address this limitation, we'll set up a Cloud function that will either archive or delete older messages.
And this has the added benefit of keeping the document size relatively small, so the initial load should always be very performant.
If we look at the actual database, you can see that each chat has an owner and then each individual message is an object inside this messages array.
It contains the user ID, a created at timestamp and the message content.
When a user is logged in and they create a new chat, it will create this document with their user ID as the owner ID.
This will give us a URL that the user can then share, so other authenticated users can use this link and then catered users can use this link and then participate in the chat.
Currently, all the chats are public, but check out Episode 75 if you want to learn more about user authorization in Firestore.
App.
Now, let's get to building this thing.
The first thing you'll need is an Angular app with AngularFire and Firebase installed.
I'm not going to cover every single line code in detail, but you can find the full source code on angularfirebase.com.
We have 3 main resources in our code that we'll focus on.
The first one is our authentication service which gets a user logged in and saves their information to Firestore.
Then we have a chat service which handles all the interaction with the chat like creating new ones and adding messages to it.
And then we have a Chat component which handles displaying the actual chat information in the UI.
The Chat component will be loaded by the Angular router and you can see here that it's nested under a dynamic ID, so we can save each chat under its own unique URL.
The first thing we'll look at is the auth service and we're not going to go through it line by line, but I just want to show you generally what's going on.
You can get a more detailed explanation in Episode 55.
The first thing we're doing is we're listening to the AngularFire auth state, which is the currently authenticated user.
And then we're going into Firestore and grabbing a related user document with their profile information.
Then, we also have a handful of methods to sign the user in with Google and also sign out.
And the one unique line of code that you'll see throughout App's video is this GetUser method, which converts the user observable to a Promise, so we can later use it with AsyncAwait.
This is just much easier to work with when it comes to writing data to Firestore.
So now that we have a way to manage user data, we're going to go into our Chat Service and build out the actual methods that control our chat.
The service will use AngularFirestore along with our auth service, the router and will also import Firestore directly from Firebase app.
The first method we'll write is git chat which just retrieves a chat document from the Firestore database.
We're going to want the chat document to be retrieved as an observable because it will constantly changing.
And we'll also wanna tell AngularFire to map the document ID along with the data payload as the final result from this observable.
This will just make it easier to pass the document ID around once we get to the main component here in a few minutes.
The next method create will add a new document to the database.
We can make it an async function because all write methods in Firebase are Promise based.
When we fire this create method, we want to ensure that we have the currently authenticated user's user ID.
We can get it by awaiting the Promise and then we'll set up a data payload here with the user ID, a timestamp and then also an empty array of messages.
From there, it's just a matter of making a reference to the Chats Collection and then we'll call the add method to add this data as a new document.
When that operation is complete, we'll go ahead and use the Angular router to navigate to the URL with that chat ID.
That takes care of creating a new chat room, adding messages to the chat follows a very similar process.
We'll once again create an async function that takes the Chat ID and the message content as an argument.
Then we'll determine the current user's ID with our getUser method, and then we'll set up a data payload with the user ID, the content, and a created at timestamp.
We're going to use a relatively new method in Firestore, which is the ArrayUnion method.
It changes the behavior of updates when working with Arrays.
So if we have a unique object, it's going to append that object to the end of the Array.
The nice thing about this is that it ensures every message is unique and item potent.
In other words, if we accidentally submit the same data payload twice, only one of those objects will actually be added to the chat array.
And that's actually a really nice advantage over working with a dedicated collection for your messages.
The last thing I'm gonna show you here requires some pretty advanced RxJS knowledge.
If you get completely lost with this code, it's understandable and I recommend that you watch my Firestore Joins video to get a better idea of what's going on here.
So the purpose of this method app is to join the user profiles to the individual chat messages.
This will ensure that our chat messages always show the most recent user profile Image and Username on the message itself.
This can get pretty complex, because a user can post multiple messages to a single chat room, but we don't want to redundantly we read their profile data over and over again.
So what we do is we take the original chat document and find all of the unique user IDs within that messages array.
Then, we read all those user profiles concurrently using the combineLatest method.
And lastly, we recompose the original messages object, so that every message also has a user property with the user profile data.
The end result is that we have a single object that has all the chat messages and all the user profiles ALs and everything is synced up in real time.
So that takes care of most of the hard stuff.
Now, we can get into the Chat component and build out the UI.
This component will depend on our chat service and also activated route from the Angular router.
We have 2 properties on this component.
We have the ChatObservable, which is the actual data that we want to show in the UI and then New Message, which is the form input that the user will enter to add a new message to the chat.
Now, because this component is loaded by the Angular router, we can grab the Document ID using ActivatedRoute, which we do by saying route Snapshot Param Map Git ID.
We can use that ID to first set up a source observable of the chat messages.
And you could just stop at this point if you only need the messages, but we also want to join the users to this original Observable.
So we'll set up the Chat property as the observable returned from the JoinUser's method.
For this demo, I'm just going to use the most basic type of form that you can use in Angular, which is an ngModelForm.
These are also called template driven forms and, basically, we're just going to take the value from a Form input and send it to our SendMessage method, which will update Firestore.
App Store.
And the last little tip here is that whenever working with a Real Time Array and looping over it in Angular, you should set up a TrackBy method.
This just tells Angular to only rerender items that have changed and it will dramatically improve the user experience and performance.
Now we can switch over to the HTML and we'll set up an ng if statement that unwraps that Chat Observable.
You'll notice I'm doing this on an NG container, which allows us to set up a context in this template without actually rendering any DOM elements.
So once our Chat observable data is ready, we can just use it as this chat variable throughout the template.
From there, I'll set up an ngForLoop and loop over the messages in the ChatMessages array.
Because they're all embedded on the document, we don't have to wait for anything asynchronous.
And this is the point where we'll also wanna use our trackBy function.
From there, we'll have access to the message itself plus some information about the user.
And the final step is to set up our form.
If we use app ng model, it will bind this new message property to the component typescript.
And then if the user clicks the enter key, we'll go ahead and submit their chat message.
App.
And just for good measure, we'll also add a submit button for users that don't have an enter key.
That takes care of the front end code, but there's still one thing that we have to address to really make this a usable feature.
As I mentioned earlier, this data model will only work up to 1 megabyte, at which point Firestore will start rejecting new messages.
We can actually address this with a very simple Cloud function that just listens to the incoming messages.
And if we get over a certain point, it will just either archive or delete the oldest messages.
If you look over here on the left, I'm adding new messages.
And once I get over 10, it's going to start deleting messages on the right.
That's my Cloud Function working in the background, and I don't know if something changed recently, but there's almost no latency on these requests.
The Cloud Function round trip finishes in just a few 100 milliseconds.
So I already have Cloud Functions initialized in this project with TypeScript, and the only thing I'm doing is importing the Admin SDK and initializing it.
Then we'll go ahead and define Firestore as our database.
Then we can name our function archiveChat.
Then we'll have it point to our chat collection and only run when the document is created.
The purpose of this function is to look at the current size of the messages array.
If there's more than 50 messages in there or if the messages characters are greater than 10,000, then we'll want to start deleting or archiving those messages.
It's not actually implemented here, but you could use JSON stringify to check the total length of the object as a string.
Then you can roughly estimate the total size of the document based on the length of that string.
So if we determine that the array is too big, then we'll go ahead and start splicing items from the beginning of that array or the oldest messages.
Then you'll also notice that I'm setting this up as a Batch Right, because you might want to add the deleted messages to some other location.
If that is the case, you definitely wanna is an atomic batch because you don't want the delete operation to succeed and the update to fail.
Then you would lose those messages forever.
And, lastly, we'll just go ahead and return null.
If these conditions are not met, we won't make any update to the existing document.
Now we have a basic chat feature up and running.
Make sure to check out the link in the description and play around with the live demo based on this code.
If this video helped you, please like and subscribe, and I realized that we went through that code pretty fast, but I'm working on a fully fledged chat feature for the Ionic course, which will be released in the next week or 2.
So if you want to see more features at a slightly slower pace, then I recommend upgrading to a Pro membership to get access to that course.
Thanks for watching and I'll talk to you soon.
App,

Speaker 0: Building a real time chat app used to be something that would take at least weeks of development time. But with Firebase, it becomes almost trivial because the SDK handles all of the state management and data syncing between the client and server for you. It's so easy, in fact, that I built an entire chat app in the last 24 hours that you can use right now. And over the next few minutes, I'll show you lay how I built it using AngularFire and Firestore. If you're just finding the channel, like and subscribe.

And if you leave a comment below, you'll have a chance to win either a sticker or a t shirt on the live stream later app. Now, the first thing I wanna talk about in this video is data modeling, because there's no one size fits all for every chat feature. The first thing every app will need is a user authentication system and then some public user profile information saved in a user's collection in Firestore. That's pretty straightforward, but you have a lot of different options when it comes to modeling the relationship between users, chats, and individual messages. I think the logical first approach that most people would take is to set up a collection of chats, and then for each of these chats, create a sub collection of messages.

This is a good way to go because you can save all of your Chat Metadata on one small document and then query as many messages as you need from the Sub Collection. Action. And as an alternative, you might make this a RootCollection, so you can query the messages across all of the different chats throughout your application. For example, a RootCollection would allow you to query all the messages for a specific user across all of the different chats that they're participating in. I think this approach is the most flexible, but the drawback is that you need to execute a document read for every message that you show in the UI.

Eye. In this video, we're going to take a completely different approach by modeling each Chat as its own document and then embedding the messages for that Chat on the document. This method is fast and simple, but there are some limitations. 1st, we can no longer query the individual messages because they're embedded on the document. We can only sort them client side.

And the second thing is that documents are limited to 1 megabyte of data, which is conservatively somewhere between 250 and a 1000 messages per document. To address this limitation, we'll set up a Cloud function that will either archive or delete older messages. And this has the added benefit of keeping the document size relatively small, so the initial load should always be very performant. If we look at the actual database, you can see that each chat has an owner and then each individual message is an object inside this messages array. It contains the user ID, a created at timestamp and the message content.

When a user is logged in and they create a new chat, it will create this document with their user ID as the owner ID. This will give us a URL that the user can then share, so other authenticated users can use this link and then catered users can use this link and then participate in the chat. Currently, all the chats are public, but check out Episode 75 if you want to learn more about user authorization in Firestore. App. Now, let's get to building this thing.

The first thing you'll need is an Angular app with AngularFire and Firebase installed. I'm not going to cover every single line code in detail, but you can find the full source code on angularfirebase.com. We have 3 main resources in our code that we'll focus on. The first one is our authentication service which gets a user logged in and saves their information to Firestore. Then we have a chat service which handles all the interaction with the chat like creating new ones and adding messages to it.

And then we have a Chat component which handles displaying the actual chat information in the UI. The Chat component will be loaded by the Angular router and you can see here that it's nested under a dynamic ID, so we can save each chat under its own unique URL. The first thing we'll look at is the auth service and we're not going to go through it line by line, but I just want to show you generally what's going on. You can get a more detailed explanation in Episode 55. The first thing we're doing is we're listening to the AngularFire auth state, which is the currently authenticated user.

And then we're going into Firestore and grabbing a related user document with their profile information. Then, we also have a handful of methods to sign the user in with Google and also sign out. And the one unique line of code that you'll see throughout App's video is this GetUser method, which converts the user observable to a Promise, so we can later use it with AsyncAwait. This is just much easier to work with when it comes to writing data to Firestore. So now that we have a way to manage user data, we're going to go into our Chat Service and build out the actual methods that control our chat.

The service will use AngularFirestore along with our auth service, the router and will also import Firestore directly from Firebase app. The first method we'll write is git chat which just retrieves a chat document from the Firestore database. We're going to want the chat document to be retrieved as an observable because it will constantly changing. And we'll also wanna tell AngularFire to map the document ID along with the data payload as the final result from this observable. This will just make it easier to pass the document ID around once we get to the main component here in a few minutes.

The next method create will add a new document to the database. We can make it an async function because all write methods in Firebase are Promise based. When we fire this create method, we want to ensure that we have the currently authenticated user's user ID. We can get it by awaiting the Promise and then we'll set up a data payload here with the user ID, a timestamp and then also an empty array of messages. From there, it's just a matter of making a reference to the Chats Collection and then we'll call the add method to add this data as a new document.

When that operation is complete, we'll go ahead and use the Angular router to navigate to the URL with that chat ID. That takes care of creating a new chat room, adding messages to the chat follows a very similar process. We'll once again create an async function that takes the Chat ID and the message content as an argument. Then we'll determine the current user's ID with our getUser method, and then we'll set up a data payload with the user ID, the content, and a created at timestamp. We're going to use a relatively new method in Firestore, which is the ArrayUnion method.

It changes the behavior of updates when working with Arrays. So if we have a unique object, it's going to append that object to the end of the Array. The nice thing about this is that it ensures every message is unique and item potent. In other words, if we accidentally submit the same data payload twice, only one of those objects will actually be added to the chat array. And that's actually a really nice advantage over working with a dedicated collection for your messages.

The last thing I'm gonna show you here requires some pretty advanced RxJS knowledge. If you get completely lost with this code, it's understandable and I recommend that you watch my Firestore Joins video to get a better idea of what's going on here. So the purpose of this method app is to join the user profiles to the individual chat messages. This will ensure that our chat messages always show the most recent user profile Image and Username on the message itself. This can get pretty complex, because a user can post multiple messages to a single chat room, but we don't want to redundantly we read their profile data over and over again.

So what we do is we take the original chat document and find all of the unique user IDs within that messages array. Then, we read all those user profiles concurrently using the combineLatest method. And lastly, we recompose the original messages object, so that every message also has a user property with the user profile data. The end result is that we have a single object that has all the chat messages and all the user profiles ALs and everything is synced up in real time. So that takes care of most of the hard stuff.

Now, we can get into the Chat component and build out the UI. This component will depend on our chat service and also activated route from the Angular router. We have 2 properties on this component. We have the ChatObservable, which is the actual data that we want to show in the UI and then New Message, which is the form input that the user will enter to add a new message to the chat. Now, because this component is loaded by the Angular router, we can grab the Document ID using ActivatedRoute, which we do by saying route Snapshot Param Map Git ID.

We can use that ID to first set up a source observable of the chat messages. And you could just stop at this point if you only need the messages, but we also want to join the users to this original Observable. So we'll set up the Chat property as the observable returned from the JoinUser's method. For this demo, I'm just going to use the most basic type of form that you can use in Angular, which is an ngModelForm. These are also called template driven forms and, basically, we're just going to take the value from a Form input and send it to our SendMessage method, which will update Firestore.

App Store. And the last little tip here is that whenever working with a Real Time Array and looping over it in Angular, you should set up a TrackBy method. This just tells Angular to only rerender items that have changed and it will dramatically improve the user experience and performance. Now we can switch over to the HTML and we'll set up an ng if statement that unwraps that Chat Observable. You'll notice I'm doing this on an NG container, which allows us to set up a context in this template without actually rendering any DOM elements.

So once our Chat observable data is ready, we can just use it as this chat variable throughout the template. From there, I'll set up an ngForLoop and loop over the messages in the ChatMessages array. Because they're all embedded on the document, we don't have to wait for anything asynchronous. And this is the point where we'll also wanna use our trackBy function. From there, we'll have access to the message itself plus some information about the user.

And the final step is to set up our form. If we use app ng model, it will bind this new message property to the component typescript. And then if the user clicks the enter key, we'll go ahead and submit their chat message. App. And just for good measure, we'll also add a submit button for users that don't have an enter key.

That takes care of the front end code, but there's still one thing that we have to address to really make this a usable feature. As I mentioned earlier, this data model will only work up to 1 megabyte, at which point Firestore will start rejecting new messages. We can actually address this with a very simple Cloud function that just listens to the incoming messages. And if we get over a certain point, it will just either archive or delete the oldest messages. If you look over here on the left, I'm adding new messages.

And once I get over 10, it's going to start deleting messages on the right. That's my Cloud Function working in the background, and I don't know if something changed recently, but there's almost no latency on these requests. The Cloud Function round trip finishes in just a few 100 milliseconds. So I already have Cloud Functions initialized in this project with TypeScript, and the only thing I'm doing is importing the Admin SDK and initializing it. Then we'll go ahead and define Firestore as our database.

Then we can name our function archiveChat. Then we'll have it point to our chat collection and only run when the document is created. The purpose of this function is to look at the current size of the messages array. If there's more than 50 messages in there or if the messages characters are greater than 10,000, then we'll want to start deleting or archiving those messages. It's not actually implemented here, but you could use JSON stringify to check the total length of the object as a string.

Then you can roughly estimate the total size of the document based on the length of that string. So if we determine that the array is too big, then we'll go ahead and start splicing items from the beginning of that array or the oldest messages. Then you'll also notice that I'm setting this up as a Batch Right, because you might want to add the deleted messages to some other location. If that is the case, you definitely wanna is an atomic batch because you don't want the delete operation to succeed and the update to fail. Then you would lose those messages forever.

And, lastly, we'll just go ahead and return null. If these conditions are not met, we won't make any update to the existing document. Now we have a basic chat feature up and running. Make sure to check out the link in the description and play around with the live demo based on this code. If this video helped you, please like and subscribe, and I realized that we went through that code pretty fast, but I'm working on a fully fledged chat feature for the Ionic course, which will be released in the next week or 2.

So if you want to see more features at a slightly slower pace, then I recommend upgrading to a Pro membership to get access to that course. Thanks for watching and I'll talk to you soon. App,
Building a real time chat app used to be something that would take at least weeks of development time. But with Firebase, it becomes almost trivial because the SDK handles all of the state management and data syncing between the client and server for you. It's so easy, in fact, that I built an entire chat app in the last 24 hours that you can use right now. And over the next few minutes, I'll show you lay how I built it using AngularFire and Firestore.
If you're just finding the channel, like and subscribe. And if you leave a comment below, you'll have a chance to win either a sticker or a t shirt on the live stream later app. Now, the first thing I wanna talk about in this video is data modeling, because there's no one size fits all for every chat feature. The first thing every app will need is a user authentication system and then some public user profile information saved in a user's collection in Firestore. That's pretty straightforward, but you have a lot of different options when it comes to modeling the relationship between users, chats, and individual messages. I think the logical first approach that most people would take is to set up a collection of chats, and then for each of these chats, create a sub collection of messages.
streaming
television
youtube
social media
multimedia
modeling
security
This is a good way to go because you can save all of your Chat Metadata on one small document and then query as many messages as you need from the Sub Collection. Action. And as an alternative, you might make this a RootCollection, so you can query the messages across all of the different chats throughout your application. For example, a RootCollection would allow you to query all the messages for a specific user across all of the different chats that they're participating in.
I think this approach is the most flexible, but the drawback is that you need to execute a document read for every message that you show in the UI. Eye. In this video, we're going to take a completely different approach by modeling each Chat as its own document and then embedding the messages for that Chat on the document. This method is fast and simple, but there are some limitations. 1st, we can no longer query the individual messages because they're embedded on the document.
programming
modeling
visualization
multimedia
We can only sort them client side. And the second thing is that documents are limited to 1 megabyte of data, which is conservatively somewhere between 250 and a 1000 messages per document.
To address this limitation, we'll set up a Cloud function that will either archive or delete older messages. And this has the added benefit of keeping the document size relatively small, so the initial load should always be very performant. If we look at the actual database, you can see that each chat has an owner and then each individual message is an object inside this messages array. It contains the user ID, a created at timestamp and the message content. When a user is logged in and they create a new chat, it will create this document with their user ID as the owner ID.
databases
This will give us a URL that the user can then share, so other authenticated users can use this link and then catered users can use this link and then participate in the chat. Currently, all the chats are public, but check out Episode 75 if you want to learn more about user authorization in Firestore. App. Now, let's get to building this thing. The first thing you'll need is an Angular app with AngularFire and Firebase installed. I'm not going to cover every single line code in detail, but you can find the full source code on angularfirebase.com.
We have 3 main resources in our code that we'll focus on. The first one is our authentication service which gets a user logged in and saves their information to Firestore. Then we have a chat service which handles all the interaction with the chat like creating new ones and adding messages to it. And then we have a Chat component which handles displaying the actual chat information in the UI. The Chat component will be loaded by the Angular router and you can see here that it's nested under a dynamic ID, so we can save each chat under its own unique URL.
networking
programming
software
The first thing we'll look at is the auth service and we're not going to go through it line by line, but I just want to show you generally what's going on. You can get a more detailed explanation in Episode 55. The first thing we're doing is we're listening to the AngularFire auth state, which is the currently authenticated user. And then we're going into Firestore and grabbing a related user document with their profile information. Then, we also have a handful of methods to sign the user in with Google and also sign out.
And the one unique line of code that you'll see throughout App's video is this GetUser method, which converts the user observable to a Promise, so we can later use it with AsyncAwait. This is just much easier to work with when it comes to writing data to Firestore. So now that we have a way to manage user data, we're going to go into our Chat Service and build out the actual methods that control our chat.
programming
The service will use AngularFirestore along with our auth service, the router and will also import Firestore directly from Firebase app. The first method we'll write is git chat which just retrieves a chat document from the Firestore database. We're going to want the chat document to be retrieved as an observable because it will constantly changing. And we'll also wanna tell AngularFire to map the document ID along with the data payload as the final result from this observable. This will just make it easier to pass the document ID around once we get to the main component here in a few minutes. The next method create will add a new document to the database. We can make it an async function because all write methods in Firebase are Promise based. When we fire this create method, we want to ensure that we have the currently authenticated user's user ID. We can get it by awaiting the Promise and then we'll set up a data payload here with the user ID, a timestamp and then also an empty array of messages. From there, it's just a matter of making a reference to the Chats Collection and then we'll call the add method to add this data as a new document. When that operation is complete, we'll go ahead and use the Angular router to navigate to the URL with that chat ID. That takes care of creating a new chat room, adding messages to the chat follows a very similar process. We'll once again create an async function that takes the Chat ID and the message content as an argument. Then we'll determine the current user's ID with our getUser method, and then we'll set up a data payload with the user ID, the content, and a created at timestamp. We're going to use a relatively new method in Firestore, which is the ArrayUnion method. It changes the behavior of updates when working with Arrays. So if we have a unique object, it's going to append that object to the end of the Array. The nice thing about this is that it ensures every message is unique and item potent.
In other words, if we accidentally submit the same data payload twice, only one of those objects will actually be added to the chat array. And that's actually a really nice advantage over working with a dedicated collection for your messages. The last thing I'm gonna show you here requires some pretty advanced RxJS knowledge.
data
If you get completely lost with this code, it's understandable and I recommend that you watch my Firestore Joins video to get a better idea of what's going on here. So the purpose of this method app is to join the user profiles to the individual chat messages. This will ensure that our chat messages always show the most recent user profile Image and Username on the message itself. This can get pretty complex, because a user can post multiple messages to a single chat room, but we don't want to redundantly we read their profile data over and over again. So what we do is we take the original chat document and find all of the unique user IDs within that messages array. Then, we read all those user profiles concurrently using the combineLatest method. And lastly, we recompose the original messages object, so that every message also has a user property with the user profile data. The end result is that we have a single object that has all the chat messages and all the user profiles ALs and everything is synced up in real time. So that takes care of most of the hard stuff. Now, we can get into the Chat component and build out the UI. This component will depend on our chat service and also activated route from the Angular router. We have 2 properties on this component. We have the ChatObservable, which is the actual data that we want to show in the UI and then New Message, which is the form input that the user will enter to add a new message to the chat. Now, because this component is loaded by the Angular router, we can grab the Document ID using ActivatedRoute, which we do by saying route Snapshot Param Map Git ID. We can use that ID to first set up a source observable of the chat messages. And you could just stop at this point if you only need the messages, but we also want to join the users to this original Observable. So we'll set up the Chat property as the observable returned from the JoinUser's method. For this demo, I'm just going to use the most basic type of form that you can use in Angular, which is an ngModelForm. These are also called template driven forms and, basically, we're just going to take the value from a Form input and send it to our SendMessage method, which will update Firestore. App Store. And the last little tip here is that whenever working with a Real Time Array and looping over it in Angular, you should set up a TrackBy method. This just tells Angular to only rerender items that have changed and it will dramatically improve the user experience and performance. Now we can switch over to the HTML and we'll set up an ng if statement that unwraps that Chat Observable. You'll notice I'm doing this on an NG container, which allows us to set up a context in this template without actually rendering any DOM elements.
So once our Chat observable data is ready, we can just use it as this chat variable throughout the template. From there, I'll set up an ngForLoop and loop over the messages in the ChatMessages array. Because they're all embedded on the document, we don't have to wait for anything asynchronous. And this is the point where we'll also wanna use our trackBy function. From there, we'll have access to the message itself plus some information about the user. And the final step is to set up our form.
programming
If we use app ng model, it will bind this new message property to the component typescript. And then if the user clicks the enter key, we'll go ahead and submit their chat message. App. And just for good measure, we'll also add a submit button for users that don't have an enter key. That takes care of the front end code, but there's still one thing that we have to address to really make this a usable feature. As I mentioned earlier, this data model will only work up to 1 megabyte, at which point Firestore will start rejecting new messages. We can actually address this with a very simple Cloud function that just listens to the incoming messages. And if we get over a certain point, it will just either archive or delete the oldest messages. If you look over here on the left, I'm adding new messages. And once I get over 10, it's going to start deleting messages on the right. That's my Cloud Function working in the background, and I don't know if something changed recently, but there's almost no latency on these requests. The Cloud Function round trip finishes in just a few 100 milliseconds. So I already have Cloud Functions initialized in this project with TypeScript, and the only thing I'm doing is importing the Admin SDK and initializing it.
Then we'll go ahead and define Firestore as our database. Then we can name our function archiveChat. Then we'll have it point to our chat collection and only run when the document is created. The purpose of this function is to look at the current size of the messages array.
databases
If there's more than 50 messages in there or if the messages characters are greater than 10,000, then we'll want to start deleting or archiving those messages. It's not actually implemented here, but you could use JSON stringify to check the total length of the object as a string. Then you can roughly estimate the total size of the document based on the length of that string. So if we determine that the array is too big, then we'll go ahead and start splicing items from the beginning of that array or the oldest messages. Then you'll also notice that I'm setting this up as a Batch Right, because you might want to add the deleted messages to some other location. If that is the case, you definitely wanna is an atomic batch because you don't want the delete operation to succeed and the update to fail. Then you would lose those messages forever. And, lastly, we'll just go ahead and return null. If these conditions are not met, we won't make any update to the existing document. Now we have a basic chat feature up and running. Make sure to check out the link in the description and play around with the live demo based on this code. If this video helped you, please like and subscribe, and I realized that we went through that code pretty fast, but I'm working on a fully fledged chat feature for the Ionic course, which will be released in the next week or 2. So if you want to see more features at a slightly slower pace, then I recommend upgrading to a Pro membership to get access to that course.
Thanks for watching and I'll talk to you soon. App,
Building a real time chat app used to be something that would take at least weeks of development time. But with Firebase, it becomes almost trivial because the SDK handles all of the state management and data syncing between the client and server for you. It's so easy, in fact, that I built an entire chat app in the last 24 hours that you can use right now. And over the next few minutes, I'll show you lay how I built it using AngularFire and Firestore. If you're just finding the channel, like and subscribe. And if you leave a comment below, you'll have a chance to win either a sticker or a t shirt on the live stream later app. Now, the first thing I wanna talk about in this video is data modeling, because there's no one size fits all for every chat feature. The first thing every app will need is a user authentication system and then some public user profile information saved in a user's collection in Firestore. That's pretty straightforward, but you have a lot of different options when it comes to modeling the relationship between users, chats, and individual messages. I think the logical first approach that most people would take is to set up a collection of chats, and then for each of these chats, create a sub collection of messages. This is a good way to go because you can save all of your Chat Metadata on one small document and then query as many messages as you need from the Sub Collection. Action. And as an alternative, you might make this a RootCollection, so you can query the messages across all of the different chats throughout your application. For example, a RootCollection would allow you to query all the messages for a specific user across all of the different chats that they're participating in. I think this approach is the most flexible, but the drawback is that you need to execute a document read for every message that you show in the UI. Eye. In this video, we're going to take a completely different approach by modeling each Chat as its own document and then embedding the messages for that Chat on the document. This method is fast and simple, but there are some limitations. 1st, we can no longer query the individual messages because they're embedded on the document. We can only sort them client side. And the second thing is that documents are limited to 1 megabyte of data, which is conservatively somewhere between 250 and a 1000 messages per document. To address this limitation, we'll set up a Cloud function that will either archive or delete older messages. And this has the added benefit of keeping the document size relatively small, so the initial load should always be very performant. If we look at the actual database, you can see that each chat has an owner and then each individual message is an object inside this messages array. It contains the user ID, a created at timestamp and the message content. When a user is logged in and they create a new chat, it will create this document with their user ID as the owner ID. This will give us a URL that the user can then share, so other authenticated users can use this link and then catered users can use this link and then participate in the chat. Currently, all the chats are public, but check out Episode 75 if you want to learn more about user authorization in Firestore. App. Now, let's get to building this thing. The first thing you'll need is an Angular app with AngularFire and Firebase installed. I'm not going to cover every single line code in detail, but you can find the full source code on angularfirebase.com. We have 3 main resources in our code that we'll focus on. The first one is our authentication service which gets a user logged in and saves their information to Firestore. Then we have a chat service which handles all the interaction with the chat like creating new ones and adding messages to it. And then we have a Chat component which handles displaying the actual chat information in the UI. The Chat component will be loaded by the Angular router and you can see here that it's nested under a dynamic ID, so we can save each chat under its own unique URL. The first thing we'll look at is the auth service and we're not going to go through it line by line, but I just want to show you generally what's going on. You can get a more detailed explanation in Episode 55. The first thing we're doing is we're listening to the AngularFire auth state, which is the currently authenticated user. And then we're going into Firestore and grabbing a related user document with their profile information. Then, we also have a handful of methods to sign the user in with Google and also sign out. And the one unique line of code that you'll see throughout App's video is this GetUser method, which converts the user observable to a Promise, so we can later use it with AsyncAwait. This is just much easier to work with when it comes to writing data to Firestore. So now that we have a way to manage user data, we're going to go into our Chat Service and build out the actual methods that control our chat. The service will use AngularFirestore along with our auth service, the router and will also import Firestore directly from Firebase app. The first method we'll write is git chat which just retrieves a chat document from the Firestore database. We're going to want the chat document to be retrieved as an observable because it will constantly changing. And we'll also wanna tell AngularFire to map the document ID along with the data payload as the final result from this observable. This will just make it easier to pass the document ID around once we get to the main component here in a few minutes. The next method create will add a new document to the database. We can make it an async function because all write methods in Firebase are Promise based. When we fire this create method, we want to ensure that we have the currently authenticated user's user ID. We can get it by awaiting the Promise and then we'll set up a data payload here with the user ID, a timestamp and then also an empty array of messages. From there, it's just a matter of making a reference to the Chats Collection and then we'll call the add method to add this data as a new document. When that operation is complete, we'll go ahead and use the Angular router to navigate to the URL with that chat ID. That takes care of creating a new chat room, adding messages to the chat follows a very similar process. We'll once again create an async function that takes the Chat ID and the message content as an argument. Then we'll determine the current user's ID with our getUser method, and then we'll set up a data payload with the user ID, the content, and a created at timestamp. We're going to use a relatively new method in Firestore, which is the ArrayUnion method. It changes the behavior of updates when working with Arrays. So if we have a unique object, it's going to append that object to the end of the Array. The nice thing about this is that it ensures every message is unique and item potent. In other words, if we accidentally submit the same data payload twice, only one of those objects will actually be added to the chat array. And that's actually a really nice advantage over working with a dedicated collection for your messages. The last thing I'm gonna show you here requires some pretty advanced RxJS knowledge. If you get completely lost with this code, it's understandable and I recommend that you watch my Firestore Joins video to get a better idea of what's going on here. So the purpose of this method app is to join the user profiles to the individual chat messages. This will ensure that our chat messages always show the most recent user profile Image and Username on the message itself. This can get pretty complex, because a user can post multiple messages to a single chat room, but we don't want to redundantly we read their profile data over and over again. So what we do is we take the original chat document and find all of the unique user IDs within that messages array. Then, we read all those user profiles concurrently using the combineLatest method. And lastly, we recompose the original messages object, so that every message also has a user property with the user profile data. The end result is that we have a single object that has all the chat messages and all the user profiles ALs and everything is synced up in real time. So that takes care of most of the hard stuff. Now, we can get into the Chat component and build out the UI. This component will depend on our chat service and also activated route from the Angular router. We have 2 properties on this component. We have the ChatObservable, which is the actual data that we want to show in the UI and then New Message, which is the form input that the user will enter to add a new message to the chat. Now, because this component is loaded by the Angular router, we can grab the Document ID using ActivatedRoute, which we do by saying route Snapshot Param Map Git ID. We can use that ID to first set up a source observable of the chat messages. And you could just stop at this point if you only need the messages, but we also want to join the users to this original Observable. So we'll set up the Chat property as the observable returned from the JoinUser's method. For this demo, I'm just going to use the most basic type of form that you can use in Angular, which is an ngModelForm. These are also called template driven forms and, basically, we're just going to take the value from a Form input and send it to our SendMessage method, which will update Firestore. App Store. And the last little tip here is that whenever working with a Real Time Array and looping over it in Angular, you should set up a TrackBy method. This just tells Angular to only rerender items that have changed and it will dramatically improve the user experience and performance. Now we can switch over to the HTML and we'll set up an ng if statement that unwraps that Chat Observable. You'll notice I'm doing this on an NG container, which allows us to set up a context in this template without actually rendering any DOM elements. So once our Chat observable data is ready, we can just use it as this chat variable throughout the template. From there, I'll set up an ngForLoop and loop over the messages in the ChatMessages array. Because they're all embedded on the document, we don't have to wait for anything asynchronous. And this is the point where we'll also wanna use our trackBy function. From there, we'll have access to the message itself plus some information about the user. And the final step is to set up our form. If we use app ng model, it will bind this new message property to the component typescript. And then if the user clicks the enter key, we'll go ahead and submit their chat message. App. And just for good measure, we'll also add a submit button for users that don't have an enter key. That takes care of the front end code, but there's still one thing that we have to address to really make this a usable feature. As I mentioned earlier, this data model will only work up to 1 megabyte, at which point Firestore will start rejecting new messages. We can actually address this with a very simple Cloud function that just listens to the incoming messages. And if we get over a certain point, it will just either archive or delete the oldest messages. If you look over here on the left, I'm adding new messages. And once I get over 10, it's going to start deleting messages on the right. That's my Cloud Function working in the background, and I don't know if something changed recently, but there's almost no latency on these requests. The Cloud Function round trip finishes in just a few 100 milliseconds. So I already have Cloud Functions initialized in this project with TypeScript, and the only thing I'm doing is importing the Admin SDK and initializing it. Then we'll go ahead and define Firestore as our database. Then we can name our function archiveChat. Then we'll have it point to our chat collection and only run when the document is created. The purpose of this function is to look at the current size of the messages array. If there's more than 50 messages in there or if the messages characters are greater than 10,000, then we'll want to start deleting or archiving those messages. It's not actually implemented here, but you could use JSON stringify to check the total length of the object as a string. Then you can roughly estimate the total size of the document based on the length of that string. So if we determine that the array is too big, then we'll go ahead and start splicing items from the beginning of that array or the oldest messages. Then you'll also notice that I'm setting this up as a Batch Right, because you might want to add the deleted messages to some other location. If that is the case, you definitely wanna is an atomic batch because you don't want the delete operation to succeed and the update to fail. Then you would lose those messages forever. And, lastly, we'll just go ahead and return null. If these conditions are not met, we won't make any update to the existing document. Now we have a basic chat feature up and running. Make sure to check out the link in the description and play around with the live demo based on this code. If this video helped you, please like and subscribe, and I realized that we went through that code pretty fast, but I'm working on a fully fledged chat feature for the Ionic course, which will be released in the next week or 2. So if you want to see more features at a slightly slower pace, then I recommend upgrading to a Pro membership to get access to that course. Thanks for watching and I'll talk to you soon. App,
Building
building
a
a
real
real
time
time
chat
chat
app
app
used
used
to
to
be
be
something
something
that
that
would
would
take
take
at
at
least
least
weeks
weeks
of
of
development
development
time.
time
But
but
with
with
Firebase,
firebase
it
it
becomes
becomes
almost
almost
trivial
trivial
because
because
the
the
SDK
sdk
handles
handles
all
all
of
of
the
the
state
state
management
management
and
and
data
data
syncing
syncing
between
between
the
the
client
client
and
and
server
server
for
for
you.
you
It's
it's
so
so
easy,
easy
in
in
fact,
fact
that
that
I
i
built
built
an
an
entire
entire
chat
chat
app
app
in
in
the
the
last
last
24
24
hours
hours
that
that
you
you
can
can
use
use
right
right
now.
now
And
and
over
over
the
the
next
next
few
few
minutes,
minutes
I'll
i'll
show
show
you
you
lay
lay
how
how
I
i
built
built
it
it
using
using
AngularFire
angularfire
and
and
Firestore.
firestore
If
if
you're
you're
just
just
finding
finding
the
the
channel,
channel
like
like
and
and
subscribe.
subscribe
And
and
if
if
you
you
leave
leave
a
a
comment
comment
below,
below
you'll
you'll
have
have
a
a
chance
chance
to
to
win
win
either
either
a
a
sticker
sticker
or
or
a
a
t
t
shirt
shirt
on
on
the
the
live
live
stream
stream
later
later
app.
app
Now,
now
the
the
first
first
thing
thing
I
i
wanna
wanna
talk
talk
about
about
in
in
this
this
video
video
is
is
data
data
modeling,
modeling
because
because
there's
there's
no
no
one
one
size
size
fits
fits
all
all
for
for
every
every
chat
chat
feature.
feature
The
the
first
first
thing
thing
every
every
app
app
will
will
need
need
is
is
a
a
user
user
authentication
authentication
system
system
and
and
then
then
some
some
public
public
user
user
profile
profile
information
information
saved
saved
in
in
a
a
user's
user's
collection
collection
in
in
Firestore.
firestore
That's
that's
pretty
pretty
straightforward,
straightforward
but
but
you
you
have
have
a
a
lot
lot
of
of
different
different
options
options
when
when
it
it
comes
comes
to
to
modeling
modeling
the
the
relationship
relationship
between
between
users,
users
chats,
chats
and
and
individual
individual
messages.
messages
I
i
think
think
the
the
logical
logical
first
first
approach
approach
that
that
most
most
people
people
would
would
take
take
is
is
to
to
set
set
up
up
a
a
collection
collection
of
of
chats,
chats
and
and
then
then
for
for
each
each
of
of
these
these
chats,
chats
create
create
a
a
sub
sub
collection
collection
of
of
messages.
messages
This
this
is
is
a
a
good
good
way
way
to
to
go
go
because
because
you
you
can
can
save
save
all
all
of
of
your
your
Chat
chat
Metadata
metadata
on
on
one
one
small
small
document
document
and
and
then
then
query
query
as
as
many
many
messages
messages
as
as
you
you
need
need
from
from
the
the
Sub
sub
Collection.
collection
Action.
action
And
and
as
as
an
an
alternative,
alternative
you
you
might
might
make
make
this
this
a
a
RootCollection,
rootcollection
so
so
you
you
can
can
query
query
the
the
messages
messages
across
across
all
all
of
of
the
the
different
different
chats
chats
throughout
throughout
your
your
application.
application
For
for
example,
example
a
a
RootCollection
rootcollection
would
would
allow
allow
you
you
to
to
query
query
all
all
the
the
messages
messages
for
for
a
a
specific
specific
user
user
across
across
all
all
of
of
the
the
different
different
chats
chats
that
that
they're
they're
participating
participating
in.
in
I
i
think
think
this
this
approach
approach
is
is
the
the
most
most
flexible,
flexible
but
but
the
the
drawback
drawback
is
is
that
that
you
you
need
need
to
to
execute
execute
a
a
document
document
read
read
for
for
every
every
message
message
that
that
you
you
show
show
in
in
the
the
UI.
ui
Eye.
eye
In
in
this
this
video,
video
we're
we're
going
going
to
to
take
take
a
a
completely
completely
different
different
approach
approach
by
by
modeling
modeling
each
each
Chat
chat
as
as
its
its
own
own
document
document
and
and
then
then
embedding
embedding
the
the
messages
messages
for
for
that
that
Chat
chat
on
on
the
the
document.
document
This
this
method
method
is
is
fast
fast
and
and
simple,
simple
but
but
there
there
are
are
some
some
limitations.
limitations
1st,
1st
we
we
can
can
no
no
longer
longer
query
query
the
the
individual
individual
messages
messages
because
because
they're
they're
embedded
embedded
on
on
the
the
document.
document
We
we
can
can
only
only
sort
sort
them
them
client
client
side.
side
And
and
the
the
second
second
thing
thing
is
is
that
that
documents
documents
are
are
limited
limited
to
to
1
1
megabyte
megabyte
of
of
data,
data
which
which
is
is
conservatively
conservatively
somewhere
somewhere
between
between
250
250
and
and
a
a
1000
1000
messages
messages
per
per
document.
document
To
to
address
address
this
this
limitation,
limitation
we'll
we'll
set
set
up
up
a
a
Cloud
cloud
function
function
that
that
will
will
either
either
archive
archive
or
or
delete
delete
older
older
messages.
messages
And
and
this
this
has
has
the
the
added
added
benefit
benefit
of
of
keeping
keeping
the
the
document
document
size
size
relatively
relatively
small,
small
so
so
the
the
initial
initial
load
load
should
should
always
always
be
be
very
very
performant.
performant
If
if
we
we
look
look
at
at
the
the
actual
actual
database,
database
you
you
can
can
see
see
that
that
each
each
chat
chat
has
has
an
an
owner
owner
and
and
then
then
each
each
individual
individual
message
message
is
is
an
an
object
object
inside
inside
this
this
messages
messages
array.
array
It
it
contains
contains
the
the
user
user
ID,
id
a
a
created
created
at
at
timestamp
timestamp
and
and
the
the
message
message
content.
content
When
when
a
a
user
user
is
is
logged
logged
in
in
and
and
they
they
create
create
a
a
new
new
chat,
chat
it
it
will
will
create
create
this
this
document
document
with
with
their
their
user
user
ID
id
as
as
the
the
owner
owner
ID.
id
This
this
will
will
give
give
us
us
a
a
URL
url
that
that
the
the
user
user
can
can
then
then
share,
share
so
so
other
other
authenticated
authenticated
users
users
can
can
use
use
this
this
link
link
and
and
then
then
catered
catered
users
users
can
can
use
use
this
this
link
link
and
and
then
then
participate
participate
in
in
the
the
chat.
chat
Currently,
currently
all
all
the
the
chats
chats
are
are
public,
public
but
but
check
check
out
out
Episode
episode
75
75
if
if
you
you
want
want
to
to
learn
learn
more
more
about
about
user
user
authorization
authorization
in
in
Firestore.
firestore
App.
app
Now,
now
let's
let's
get
get
to
to
building
building
this
this
thing.
thing
The
the
first
first
thing
thing
you'll
you'll
need
need
is
is
an
an
Angular
angular
app
app
with
with
AngularFire
angularfire
and
and
Firebase
firebase
installed.
installed
I'm
i'm
not
not
going
going
to
to
cover
cover
every
every
single
single
line
line
code
code
in
in
detail,
detail
but
but
you
you
can
can
find
find
the
the
full
full
source
source
code
code
on
on
angularfirebase.com.
angularfirebase.com
We
we
have
have
3
3
main
main
resources
resources
in
in
our
our
code
code
that
that
we'll
we'll
focus
focus
on.
on
The
the
first
first
one
one
is
is
our
our
authentication
authentication
service
service
which
which
gets
gets
a
a
user
user
logged
logged
in
in
and
and
saves
saves
their
their
information
information
to
to
Firestore.
firestore
Then
then
we
we
have
have
a
a
chat
chat
service
service
which
which
handles
handles
all
all
the
the
interaction
interaction
with
with
the
the
chat
chat
like
like
creating
creating
new
new
ones
ones
and
and
adding
adding
messages
messages
to
to
it.
it
And
and
then
then
we
we
have
have
a
a
Chat
chat
component
component
which
which
handles
handles
displaying
displaying
the
the
actual
actual
chat
chat
information
information
in
in
the
the
UI.
ui
The
the
Chat
chat
component
component
will
will
be
be
loaded
loaded
by
by
the
the
Angular
angular
router
router
and
and
you
you
can
can
see
see
here
here
that
that
it's
it's
nested
nested
under
under
a
a
dynamic
dynamic
ID,
id
so
so
we
we
can
can
save
save
each
each
chat
chat
under
under
its
its
own
own
unique
unique
URL.
url
The
the
first
first
thing
thing
we'll
we'll
look
look
at
at
is
is
the
the
auth
auth
service
service
and
and
we're
we're
not
not
going
going
to
to
go
go
through
through
it
it
line
line
by
by
line,
line
but
but
I
i
just
just
want
want
to
to
show
show
you
you
generally
generally
what's
what's
going
going
on.
on
You
you
can
can
get
get
a
a
more
more
detailed
detailed
explanation
explanation
in
in
Episode
episode
55.
55
The
the
first
first
thing
thing
we're
we're
doing
doing
is
is
we're
we're
listening
listening
to
to
the
the
AngularFire
angularfire
auth
auth
state,
state
which
which
is
is
the
the
currently
currently
authenticated
authenticated
user.
user
And
and
then
then
we're
we're
going
going
into
into
Firestore
firestore
and
and
grabbing
grabbing
a
a
related
related
user
user
document
document
with
with
their
their
profile
profile
information.
information
Then,
then
we
we
also
also
have
have
a
a
handful
handful
of
of
methods
methods
to
to
sign
sign
the
the
user
user
in
in
with
with
Google
google
and
and
also
also
sign
sign
out.
out
And
and
the
the
one
one
unique
unique
line
line
of
of
code
code
that
that
you'll
you'll
see
see
throughout
throughout
App's
app's
video
video
is
is
this
this
GetUser
getuser
method,
method
which
which
converts
converts
the
the
user
user
observable
observable
to
to
a
a
Promise,
promise
so
so
we
we
can
can
later
later
use
use
it
it
with
with
AsyncAwait.
asyncawait
This
this
is
is
just
just
much
much
easier
easier
to
to
work
work
with
with
when
when
it
it
comes
comes
to
to
writing
writing
data
data
to
to
Firestore.
firestore
So
so
now
now
that
that
we
we
have
have
a
a
way
way
to
to
manage
manage
user
user
data,
data
we're
we're
going
going
to
to
go
go
into
into
our
our
Chat
chat
Service
service
and
and
build
build
out
out
the
the
actual
actual
methods
methods
that
that
control
control
our
our
chat.
chat
The
the
service
service
will
will
use
use
AngularFirestore
angularfirestore
along
along
with
with
our
our
auth
auth
service,
service
the
the
router
router
and
and
will
will
also
also
import
import
Firestore
firestore
directly
directly
from
from
Firebase
firebase
app.
app
The
the
first
first
method
method
we'll
we'll
write
write
is
is
git
git
chat
chat
which
which
just
just
retrieves
retrieves
a
a
chat
chat
document
document
from
from
the
the
Firestore
firestore
database.
database
We're
we're
going
going
to
to
want
want
the
the
chat
chat
document
document
to
to
be
be
retrieved
retrieved
as
as
an
an
observable
observable
because
because
it
it
will
will
constantly
constantly
changing.
changing
And
and
we'll
we'll
also
also
wanna
wanna
tell
tell
AngularFire
angularfire
to
to
map
map
the
the
document
document
ID
id
along
along
with
with
the
the
data
data
payload
payload
as
as
the
the
final
final
result
result
from
from
this
this
observable.
observable
This
this
will
will
just
just
make
make
it
it
easier
easier
to
to
pass
pass
the
the
document
document
ID
id
around
around
once
once
we
we
get
get
to
to
the
the
main
main
component
component
here
here
in
in
a
a
few
few
minutes.
minutes
The
the
next
next
method
method
create
create
will
will
add
add
a
a
new
new
document
document
to
to
the
the
database.
database
We
we
can
can
make
make
it
it
an
an
async
async
function
function
because
because
all
all
write
write
methods
methods
in
in
Firebase
firebase
are
are
Promise
promise
based.
based
When
when
we
we
fire
fire
this
this
create
create
method,
method
we
we
want
want
to
to
ensure
ensure
that
that
we
we
have
have
the
the
currently
currently
authenticated
authenticated
user's
user's
user
user
ID.
id
We
we
can
can
get
get
it
it
by
by
awaiting
awaiting
the
the
Promise
promise
and
and
then
then
we'll
we'll
set
set
up
up
a
a
data
data
payload
payload
here
here
with
with
the
the
user
user
ID,
id
a
a
timestamp
timestamp
and
and
then
then
also
also
an
an
empty
empty
array
array
of
of
messages.
messages
From
from
there,
there
it's
it's
just
just
a
a
matter
matter
of
of
making
making
a
a
reference
reference
to
to
the
the
Chats
chats
Collection
collection
and
and
then
then
we'll
we'll
call
call
the
the
add
add
method
method
to
to
add
add
this
this
data
data
as
as
a
a
new
new
document.
document
When
when
that
that
operation
operation
is
is
complete,
complete
we'll
we'll
go
go
ahead
ahead
and
and
use
use
the
the
Angular
angular
router
router
to
to
navigate
navigate
to
to
the
the
URL
url
with
with
that
that
chat
chat
ID.
id
That
that
takes
takes
care
care
of
of
creating
creating
a
a
new
new
chat
chat
room,
room
adding
adding
messages
messages
to
to
the
the
chat
chat
follows
follows
a
a
very
very
similar
similar
process.
process
We'll
we'll
once
once
again
again
create
create
an
an
async
async
function
function
that
that
takes
takes
the
the
Chat
chat
ID
id
and
and
the
the
message
message
content
content
as
as
an
an
argument.
argument
Then
then
we'll
we'll
determine
determine
the
the
current
current
user's
user's
ID
id
with
with
our
our
getUser
getuser
method,
method
and
and
then
then
we'll
we'll
set
set
up
up
a
a
data
data
payload
payload
with
with
the
the
user
user
ID,
id
the
the
content,
content
and
and
a
a
created
created
at
at
timestamp.
timestamp
We're
we're
going
going
to
to
use
use
a
a
relatively
relatively
new
new
method
method
in
in
Firestore,
firestore
which
which
is
is
the
the
ArrayUnion
arrayunion
method.
method
It
it
changes
changes
the
the
behavior
behavior
of
of
updates
updates
when
when
working
working
with
with
Arrays.
arrays
So
so
if
if
we
we
have
have
a
a
unique
unique
object,
object
it's
it's
going
going
to
to
append
append
that
that
object
object
to
to
the
the
end
end
of
of
the
the
Array.
array
The
the
nice
nice
thing
thing
about
about
this
this
is
is
that
that
it
it
ensures
ensures
every
every
message
message
is
is
unique
unique
and
and
item
item
potent.
potent
In
in
other
other
words,
words
if
if
we
we
accidentally
accidentally
submit
submit
the
the
same
same
data
data
payload
payload
twice,
twice
only
only
one
one
of
of
those
those
objects
objects
will
will
actually
actually
be
be
added
added
to
to
the
the
chat
chat
array.
array
And
and
that's
that's
actually
actually
a
a
really
really
nice
nice
advantage
advantage
over
over
working
working
with
with
a
a
dedicated
dedicated
collection
collection
for
for
your
your
messages.
messages
The
the
last
last
thing
thing
I'm
i'm
gonna
gonna
show
show
you
you
here
here
requires
requires
some
some
pretty
pretty
advanced
advanced
RxJS
rxjs
knowledge.
knowledge
If
if
you
you
get
get
completely
completely
lost
lost
with
with
this
this
code,
code
it's
it's
understandable
understandable
and
and
I
i
recommend
recommend
that
that
you
you
watch
watch
my
my
Firestore
firestore
Joins
joins
video
video
to
to
get
get
a
a
better
better
idea
idea
of
of
what's
what's
going
going
on
on
here.
here
So
so
the
the
purpose
purpose
of
of
this
this
method
method
app
app
is
is
to
to
join
join
the
the
user
user
profiles
profiles
to
to
the
the
individual
individual
chat
chat
messages.
messages
This
this
will
will
ensure
ensure
that
that
our
our
chat
chat
messages
messages
always
always
show
show
the
the
most
most
recent
recent
user
user
profile
profile
Image
image
and
and
Username
username
on
on
the
the
message
message
itself.
itself
This
this
can
can
get
get
pretty
pretty
complex,
complex
because
because
a
a
user
user
can
can
post
post
multiple
multiple
messages
messages
to
to
a
a
single
single
chat
chat
room,
room
but
but
we
we
don't
don't
want
want
to
to
redundantly
redundantly
we
we
read
read
their
their
profile
profile
data
data
over
over
and
and
over
over
again.
again
So
so
what
what
we
we
do
do
is
is
we
we
take
take
the
the
original
original
chat
chat
document
document
and
and
find
find
all
all
of
of
the
the
unique
unique
user
user
IDs
ids
within
within
that
that
messages
messages
array.
array
Then,
then
we
we
read
read
all
all
those
those
user
user
profiles
profiles
concurrently
concurrently
using
using
the
the
combineLatest
combinelatest
method.
method
And
and
lastly,
lastly
we
we
recompose
recompose
the
the
original
original
messages
messages
object,
object
so
so
that
that
every
every
message
message
also
also
has
has
a
a
user
user
property
property
with
with
the
the
user
user
profile
profile
data.
data
The
the
end
end
result
result
is
is
that
that
we
we
have
have
a
a
single
single
object
object
that
that
has
has
all
all
the
the
chat
chat
messages
messages
and
and
all
all
the
the
user
user
profiles
profiles
ALs
als
and
and
everything
everything
is
is
synced
synced
up
up
in
in
real
real
time.
time
So
so
that
that
takes
takes
care
care
of
of
most
most
of
of
the
the
hard
hard
stuff.
stuff
Now,
now
we
we
can
can
get
get
into
into
the
the
Chat
chat
component
component
and
and
build
build
out
out
the
the
UI.
ui
This
this
component
component
will
will
depend
depend
on
on
our
our
chat
chat
service
service
and
and
also
also
activated
activated
route
route
from
from
the
the
Angular
angular
router.
router
We
we
have
have
2
2
properties
properties
on
on
this
this
component.
component
We
we
have
have
the
the
ChatObservable,
chatobservable
which
which
is
is
the
the
actual
actual
data
data
that
that
we
we
want
want
to
to
show
show
in
in
the
the
UI
ui
and
and
then
then
New
new
Message,
message
which
which
is
is
the
the
form
form
input
input
that
that
the
the
user
user
will
will
enter
enter
to
to
add
add
a
a
new
new
message
message
to
to
the
the
chat.
chat
Now,
now
because
because
this
this
component
component
is
is
loaded
loaded
by
by
the
the
Angular
angular
router,
router
we
we
can
can
grab
grab
the
the
Document
document
ID
id
using
using
ActivatedRoute,
activatedroute
which
which
we
we
do
do
by
by
saying
saying
route
route
Snapshot
snapshot
Param
param
Map
map
Git
git
ID.
id
We
we
can
can
use
use
that
that
ID
id
to
to
first
first
set
set
up
up
a
a
source
source
observable
observable
of
of
the
the
chat
chat
messages.
messages
And
and
you
you
could
could
just
just
stop
stop
at
at
this
this
point
point
if
if
you
you
only
only
need
need
the
the
messages,
messages
but
but
we
we
also
also
want
want
to
to
join
join
the
the
users
users
to
to
this
this
original
original
Observable.
observable
So
so
we'll
we'll
set
set
up
up
the
the
Chat
chat
property
property
as
as
the
the
observable
observable
returned
returned
from
from
the
the
JoinUser's
joinuser's
method.
method
For
for
this
this
demo,
demo
I'm
i'm
just
just
going
going
to
to
use
use
the
the
most
most
basic
basic
type
type
of
of
form
form
that
that
you
you
can
can
use
use
in
in
Angular,
angular
which
which
is
is
an
an
ngModelForm.
ngmodelform
These
these
are
are
also
also
called
called
template
template
driven
driven
forms
forms
and,
and
basically,
basically
we're
we're
just
just
going
going
to
to
take
take
the
the
value
value
from
from
a
a
Form
form
input
input
and
and
send
send
it
it
to
to
our
our
SendMessage
sendmessage
method,
method
which
which
will
will
update
update
Firestore.
firestore
App
app
Store.
store
And
and
the
the
last
last
little
little
tip
tip
here
here
is
is
that
that
whenever
whenever
working
working
with
with
a
a
Real
real
Time
time
Array
array
and
and
looping
looping
over
over
it
it
in
in
Angular,
angular
you
you
should
should
set
set
up
up
a
a
TrackBy
trackby
method.
method
This
this
just
just
tells
tells
Angular
angular
to
to
only
only
rerender
rerender
items
items
that
that
have
have
changed
changed
and
and
it
it
will
will
dramatically
dramatically
improve
improve
the
the
user
user
experience
experience
and
and
performance.
performance
Now
now
we
we
can
can
switch
switch
over
over
to
to
the
the
HTML
html
and
and
we'll
we'll
set
set
up
up
an
an
ng
ng
if
if
statement
statement
that
that
unwraps
unwraps
that
that
Chat
chat
Observable.
observable
You'll
you'll
notice
notice
I'm
i'm
doing
doing
this
this
on
on
an
an
NG
ng
container,
container
which
which
allows
allows
us
us
to
to
set
set
up
up
a
a
context
context
in
in
this
this
template
template
without
without
actually
actually
rendering
rendering
any
any
DOM
dom
elements.
elements
So
so
once
once
our
our
Chat
chat
observable
observable
data
data
is
is
ready,
ready
we
we
can
can
just
just
use
use
it
it
as
as
this
this
chat
chat
variable
variable
throughout
throughout
the
the
template.
template
From
from
there,
there
I'll
i'll
set
set
up
up
an
an
ngForLoop
ngforloop
and
and
loop
loop
over
over
the
the
messages
messages
in
in
the
the
ChatMessages
chatmessages
array.
array
Because
because
they're
they're
all
all
embedded
embedded
on
on
the
the
document,
document
we
we
don't
don't
have
have
to
to
wait
wait
for
for
anything
anything
asynchronous.
asynchronous
And
and
this
this
is
is
the
the
point
point
where
where
we'll
we'll
also
also
wanna
wanna
use
use
our
our
trackBy
trackby
function.
function
From
from
there,
there
we'll
we'll
have
have
access
access
to
to
the
the
message
message
itself
itself
plus
plus
some
some
information
information
about
about
the
the
user.
user
And
and
the
the
final
final
step
step
is
is
to
to
set
set
up
up
our
our
form.
form
If
if
we
we
use
use
app
app
ng
ng
model,
model
it
it
will
will
bind
bind
this
this
new
new
message
message
property
property
to
to
the
the
component
component
typescript.
typescript
And
and
then
then
if
if
the
the
user
user
clicks
clicks
the
the
enter
enter
key,
key
we'll
we'll
go
go
ahead
ahead
and
and
submit
submit
their
their
chat
chat
message.
message
App.
app
And
and
just
just
for
for
good
good
measure,
measure
we'll
we'll
also
also
add
add
a
a
submit
submit
button
button
for
for
users
users
that
that
don't
don't
have
have
an
an
enter
enter
key.
key
That
that
takes
takes
care
care
of
of
the
the
front
front
end
end
code,
code
but
but
there's
there's
still
still
one
one
thing
thing
that
that
we
we
have
have
to
to
address
address
to
to
really
really
make
make
this
this
a
a
usable
usable
feature.
feature
As
as
I
i
mentioned
mentioned
earlier,
earlier
this
this
data
data
model
model
will
will
only
only
work
work
up
up
to
to
1
1
megabyte,
megabyte
at
at
which
which
point
point
Firestore
firestore
will
will
start
start
rejecting
rejecting
new
new
messages.
messages
We
we
can
can
actually
actually
address
address
this
this
with
with
a
a
very
very
simple
simple
Cloud
cloud
function
function
that
that
just
just
listens
listens
to
to
the
the
incoming
incoming
messages.
messages
And
and
if
if
we
we
get
get
over
over
a
a
certain
certain
point,
point
it
it
will
will
just
just
either
either
archive
archive
or
or
delete
delete
the
the
oldest
oldest
messages.
messages
If
if
you
you
look
look
over
over
here
here
on
on
the
the
left,
left
I'm
i'm
adding
adding
new
new
messages.
messages
And
and
once
once
I
i
get
get
over
over
10,
10
it's
it's
going
going
to
to
start
start
deleting
deleting
messages
messages
on
on
the
the
right.
right
That's
that's
my
my
Cloud
cloud
Function
function
working
working
in
in
the
the
background,
background
and
and
I
i
don't
don't
know
know
if
if
something
something
changed
changed
recently,
recently
but
but
there's
there's
almost
almost
no
no
latency
latency
on
on
these
these
requests.
requests
The
the
Cloud
cloud
Function
function
round
round
trip
trip
finishes
finishes
in
in
just
just
a
a
few
few
100
100
milliseconds.
milliseconds
So
so
I
i
already
already
have
have
Cloud
cloud
Functions
functions
initialized
initialized
in
in
this
this
project
project
with
with
TypeScript,
typescript
and
and
the
the
only
only
thing
thing
I'm
i'm
doing
doing
is
is
importing
importing
the
the
Admin
admin
SDK
sdk
and
and
initializing
initializing
it.
it
Then
then
we'll
we'll
go
go
ahead
ahead
and
and
define
define
Firestore
firestore
as
as
our
our
database.
database
Then
then
we
we
can
can
name
name
our
our
function
function
archiveChat.
archivechat
Then
then
we'll
we'll
have
have
it
it
point
point
to
to
our
our
chat
chat
collection
collection
and
and
only
only
run
run
when
when
the
the
document
document
is
is
created.
created
The
the
purpose
purpose
of
of
this
this
function
function
is
is
to
to
look
look
at
at
the
the
current
current
size
size
of
of
the
the
messages
messages
array.
array
If
if
there's
there's
more
more
than
than
50
50
messages
messages
in
in
there
there
or
or
if
if
the
the
messages
messages
characters
characters
are
are
greater
greater
than
than
10,000,
10,000
then
then
we'll
we'll
want
want
to
to
start
start
deleting
deleting
or
or
archiving
archiving
those
those
messages.
messages
It's
it's
not
not
actually
actually
implemented
implemented
here,
here
but
but
you
you
could
could
use
use
JSON
json
stringify
stringify
to
to
check
check
the
the
total
total
length
length
of
of
the
the
object
object
as
as
a
a
string.
string
Then
then
you
you
can
can
roughly
roughly
estimate
estimate
the
the
total
total
size
size
of
of
the
the
document
document
based
based
on
on
the
the
length
length
of
of
that
that
string.
string
So
so
if
if
we
we
determine
determine
that
that
the
the
array
array
is
is
too
too
big,
big
then
then
we'll
we'll
go
go
ahead
ahead
and
and
start
start
splicing
splicing
items
items
from
from
the
the
beginning
beginning
of
of
that
that
array
array
or
or
the
the
oldest
oldest
messages.
messages
Then
then
you'll
you'll
also
also
notice
notice
that
that
I'm
i'm
setting
setting
this
this
up
up
as
as
a
a
Batch
batch
Right,
right
because
because
you
you
might
might
want
want
to
to
add
add
the
the
deleted
deleted
messages
messages
to
to
some
some
other
other
location.
location
If
if
that
that
is
is
the
the
case,
case
you
you
definitely
definitely
wanna
wanna
is
is
an
an
atomic
atomic
batch
batch
because
because
you
you
don't
don't
want
want
the
the
delete
delete
operation
operation
to
to
succeed
succeed
and
and
the
the
update
update
to
to
fail.
fail
Then
then
you
you
would
would
lose
lose
those
those
messages
messages
forever.
forever
And,
and
lastly,
lastly
we'll
we'll
just
just
go
go
ahead
ahead
and
and
return
return
null.
null
If
if
these
these
conditions
conditions
are
are
not
not
met,
met
we
we
won't
won't
make
make
any
any
update
update
to
to
the
the
existing
existing
document.
document
Now
now
we
we
have
have
a
a
basic
basic
chat
chat
feature
feature
up
up
and
and
running.
running
Make
make
sure
sure
to
to
check
check
out
out
the
the
link
link
in
in
the
the
description
description
and
and
play
play
around
around
with
with
the
the
live
live
demo
demo
based
based
on
on
this
this
code.
code
If
if
this
this
video
video
helped
helped
you,
you
please
please
like
like
and
and
subscribe,
subscribe
and
and
I
i
realized
realized
that
that
we
we
went
went
through
through
that
that
code
code
pretty
pretty
fast,
fast
but
but
I'm
i'm
working
working
on
on
a
a
fully
fully
fledged
fledged
chat
chat
feature
feature
for
for
the
the
Ionic
ionic
course,
course
which
which
will
will
be
be
released
released
in
in
the
the
next
next
week
week
or
or
2.
2
So
so
if
if
you
you
want
want
to
to
see
see
more
more
features
features
at
at
a
a
slightly
slightly
slower
slower
pace,
pace
then
then
I
i
recommend
recommend
upgrading
upgrading
to
to
a
a
Pro
pro
membership
membership
to
to
get
get
access
access
to
to
that
that
course.
course
Thanks
thanks
for
for
watching
watching
and
and
I'll
i'll
talk
talk
to
to
you
you
soon.
soon
App,
app
success
The speakers discuss building a real time chat app using Angularatera and Fir POS, including creating a collection of chats and modeling each chat feature. They also discuss the limitations of data modeling and the use of a Cloud function to save documents. The speakers then move on to building the app, including authentication, chat, and the import of Fir POS. They explain the use of the chat component, including authorization methods and the use of the MSR method. They also discuss setting up a new component for the chat interface, including a route for the original chat message and setting up a chunk of the original chat file. They suggest upgrading to a Pro membership for access to a live demo.
Firestore Megachat - Let's Build a Chat App
Building a real time chat app used to be something that would take at least weeks of development time. But with Firebase, it becomes almost trivial because the SDK handles all of the state management and data syncing between the client and server for you. It's so easy, in fact, that I built an entire chat app in the last 24 hours that you can use right now. And over the next few minutes, I'll show you lay how I built it using AngularFire and Firestore. If you're just finding the channel, like and subscribe. And if you leave a comment below, you'll have a chance to win either a sticker or a t shirt on the live stream later app. Now, the first thing I wanna talk about in this video is data modeling, because there's no one size fits all for every chat feature. The first thing every app will need is a user authentication system and then some public user profile information saved in a user's collection in Firestore. That's pretty straightforward, but you have a lot of different options when it comes to modeling the relationship between users, chats, and individual messages. I think the logical first approach that most people would take is to set up a collection of chats, and then for each of these chats, create a sub collection of messages. This is a good way to go because you can save all of your Chat Metadata on one small document and then query as many messages as you need from the Sub Collection. Action. And as an alternative, you might make this a RootCollection, so you can query the messages across all of the different chats throughout your application. For example, a RootCollection would allow you to query all the messages for a specific user across all of the different chats that they're participating in. I think this approach is the most flexible, but the drawback is that you need to execute a document read for every message that you show in the UI. Eye. In this video, we're going to take a completely different approach by modeling each Chat as its own document and then embedding the messages for that Chat on the document. This method is fast and simple, but there are some limitations. 1st, we can no longer query the individual messages because they're embedded on the document. We can only sort them client side. And the second thing is that documents are limited to 1 megabyte of data, which is conservatively somewhere between 250 and a 1000 messages per document. To address this limitation, we'll set up a Cloud function that will either archive or delete older messages. And this has the added benefit of keeping the document size relatively small, so the initial load should always be very performant. If we look at the actual database, you can see that each chat has an owner and then each individual message is an object inside this messages array. It contains the user ID, a created at timestamp and the message content. When a user is logged in and they create a new chat, it will create this document with their user ID as the owner ID. This will give us a URL that the user can then share, so other authenticated users can use this link and then catered users can use this link and then participate in the chat. Currently, all the chats are public, but check out Episode 75 if you want to learn more about user authorization in Firestore. App. Now, let's get to building this thing. The first thing you'll need is an Angular app with AngularFire and Firebase installed. I'm not going to cover every single line code in detail, but you can find the full source code on angularfirebase.com. We have 3 main resources in our code that we'll focus on. The first one is our authentication service which gets a user logged in and saves their information to Firestore. Then we have a chat service which handles all the interaction with the chat like creating new ones and adding messages to it. And then we have a Chat component which handles displaying the actual chat information in the UI. The Chat component will be loaded by the Angular router and you can see here that it's nested under a dynamic ID, so we can save each chat under its own unique URL. The first thing we'll look at is the auth service and we're not going to go through it line by line, but I just want to show you generally what's going on. You can get a more detailed explanation in Episode 55. The first thing we're doing is we're listening to the AngularFire auth state, which is the currently authenticated user. And then we're going into Firestore and grabbing a related user document with their profile information. Then, we also have a handful of methods to sign the user in with Google and also sign out. And the one unique line of code that you'll see throughout App's video is this GetUser method, which converts the user observable to a Promise, so we can later use it with AsyncAwait. This is just much easier to work with when it comes to writing data to Firestore. So now that we have a way to manage user data, we're going to go into our Chat Service and build out the actual methods that control our chat. The service will use AngularFirestore along with our auth service, the router and will also import Firestore directly from Firebase app. The first method we'll write is git chat which just retrieves a chat document from the Firestore database. We're going to want the chat document to be retrieved as an observable because it will constantly changing. And we'll also wanna tell AngularFire to map the document ID along with the data payload as the final result from this observable. This will just make it easier to pass the document ID around once we get to the main component here in a few minutes. The next method create will add a new document to the database. We can make it an async function because all write methods in Firebase are Promise based. When we fire this create method, we want to ensure that we have the currently authenticated user's user ID. We can get it by awaiting the Promise and then we'll set up a data payload here with the user ID, a timestamp and then also an empty array of messages. From there, it's just a matter of making a reference to the Chats Collection and then we'll call the add method to add this data as a new document. When that operation is complete, we'll go ahead and use the Angular router to navigate to the URL with that chat ID. That takes care of creating a new chat room, adding messages to the chat follows a very similar process. We'll once again create an async function that takes the Chat ID and the message content as an argument. Then we'll determine the current user's ID with our getUser method, and then we'll set up a data payload with the user ID, the content, and a created at timestamp. We're going to use a relatively new method in Firestore, which is the ArrayUnion method. It changes the behavior of updates when working with Arrays. So if we have a unique object, it's going to append that object to the end of the Array. The nice thing about this is that it ensures every message is unique and item potent. In other words, if we accidentally submit the same data payload twice, only one of those objects will actually be added to the chat array. And that's actually a really nice advantage over working with a dedicated collection for your messages. The last thing I'm gonna show you here requires some pretty advanced RxJS knowledge. If you get completely lost with this code, it's understandable and I recommend that you watch my Firestore Joins video to get a better idea of what's going on here. So the purpose of this method app is to join the user profiles to the individual chat messages. This will ensure that our chat messages always show the most recent user profile Image and Username on the message itself. This can get pretty complex, because a user can post multiple messages to a single chat room, but we don't want to redundantly we read their profile data over and over again. So what we do is we take the original chat document and find all of the unique user IDs within that messages array. Then, we read all those user profiles concurrently using the combineLatest method. And lastly, we recompose the original messages object, so that every message also has a user property with the user profile data. The end result is that we have a single object that has all the chat messages and all the user profiles ALs and everything is synced up in real time. So that takes care of most of the hard stuff. Now, we can get into the Chat component and build out the UI. This component will depend on our chat service and also activated route from the Angular router. We have 2 properties on this component. We have the ChatObservable, which is the actual data that we want to show in the UI and then New Message, which is the form input that the user will enter to add a new message to the chat. Now, because this component is loaded by the Angular router, we can grab the Document ID using ActivatedRoute, which we do by saying route Snapshot Param Map Git ID. We can use that ID to first set up a source observable of the chat messages. And you could just stop at this point if you only need the messages, but we also want to join the users to this original Observable. So we'll set up the Chat property as the observable returned from the JoinUser's method. For this demo, I'm just going to use the most basic type of form that you can use in Angular, which is an ngModelForm. These are also called template driven forms and, basically, we're just going to take the value from a Form input and send it to our SendMessage method, which will update Firestore. App Store. And the last little tip here is that whenever working with a Real Time Array and looping over it in Angular, you should set up a TrackBy method. This just tells Angular to only rerender items that have changed and it will dramatically improve the user experience and performance. Now we can switch over to the HTML and we'll set up an ng if statement that unwraps that Chat Observable. You'll notice I'm doing this on an NG container, which allows us to set up a context in this template without actually rendering any DOM elements. So once our Chat observable data is ready, we can just use it as this chat variable throughout the template. From there, I'll set up an ngForLoop and loop over the messages in the ChatMessages array. Because they're all embedded on the document, we don't have to wait for anything asynchronous. And this is the point where we'll also wanna use our trackBy function. From there, we'll have access to the message itself plus some information about the user. And the final step is to set up our form. If we use app ng model, it will bind this new message property to the component typescript. And then if the user clicks the enter key, we'll go ahead and submit their chat message. App. And just for good measure, we'll also add a submit button for users that don't have an enter key. That takes care of the front end code, but there's still one thing that we have to address to really make this a usable feature. As I mentioned earlier, this data model will only work up to 1 megabyte, at which point Firestore will start rejecting new messages. We can actually address this with a very simple Cloud function that just listens to the incoming messages. And if we get over a certain point, it will just either archive or delete the oldest messages. If you look over here on the left, I'm adding new messages. And once I get over 10, it's going to start deleting messages on the right. That's my Cloud Function working in the background, and I don't know if something changed recently, but there's almost no latency on these requests. The Cloud Function round trip finishes in just a few 100 milliseconds. So I already have Cloud Functions initialized in this project with TypeScript, and the only thing I'm doing is importing the Admin SDK and initializing it. Then we'll go ahead and define Firestore as our database. Then we can name our function archiveChat. Then we'll have it point to our chat collection and only run when the document is created. The purpose of this function is to look at the current size of the messages array. If there's more than 50 messages in there or if the messages characters are greater than 10,000, then we'll want to start deleting or archiving those messages. It's not actually implemented here, but you could use JSON stringify to check the total length of the object as a string. Then you can roughly estimate the total size of the document based on the length of that string. So if we determine that the array is too big, then we'll go ahead and start splicing items from the beginning of that array or the oldest messages. Then you'll also notice that I'm setting this up as a Batch Right, because you might want to add the deleted messages to some other location. If that is the case, you definitely wanna is an atomic batch because you don't want the delete operation to succeed and the update to fail. Then you would lose those messages forever. And, lastly, we'll just go ahead and return null. If these conditions are not met, we won't make any update to the existing document. Now we have a basic chat feature up and running. Make sure to check out the link in the description and play around with the live demo based on this code. If this video helped you, please like and subscribe, and I realized that we went through that code pretty fast, but I'm working on a fully fledged chat feature for the Ionic course, which will be released in the next week or 2. So if you want to see more features at a slightly slower pace, then I recommend upgrading to a Pro membership to get access to that course. Thanks for watching and I'll talk to you soon. App,
The speakers discuss building a real time chat app using Angularatera and Fir POS, including creating a collection of chats and modeling each chat feature. They also discuss the limitations of data modeling and the use of a Cloud function to save documents. The speakers then move on to building the app, including authentication, chat, and the import of Fir POS. They explain the use of the chat component, including authorization methods and the use of the MSR method. They also discuss setting up a new component for the chat interface, including a route for the original chat message and setting up a chunk of the original chat file. They suggest upgrading to a Pro membership for access to a live demo.

        [INST]
        You are youtuber called Fireship you make engaging high-intensity and entertaining coding tutorials and tech news. 
        you covers a wide range of topics relevant to programmers, aiming to help them learn and improve their skills quickly.
        
        Given the title of the video : Firestore Megachat - Let's Build a Chat App 
        and a small summary : The speakers discuss building a real time chat app using Angularatera and Fir POS, including creating a collection of chats and modeling each chat feature. They also discuss the limitations of data modeling and the use of a Cloud function to save documents. The speakers then move on to building the app, including authentication, chat, and the import of Fir POS. They explain the use of the chat component, including authorization methods and the use of the MSR method. They also discuss setting up a new component for the chat interface, including a route for the original chat message and setting up a chunk of the original chat file. They suggest upgrading to a Pro membership for access to a live demo.
        [/INST]
        
        Generate the video : Building a real time chat app used to be something that would take at least weeks of development time. But with Firebase, it becomes almost trivial because the SDK handles all of the state management and data syncing between the client and server for you. It's so easy, in fact, that I built an entire chat app in the last 24 hours that you can use right now. And over the next few minutes, I'll show you lay how I built it using AngularFire and Firestore. If you're just finding the channel, like and subscribe. And if you leave a comment below, you'll have a chance to win either a sticker or a t shirt on the live stream later app. Now, the first thing I wanna talk about in this video is data modeling, because there's no one size fits all for every chat feature. The first thing every app will need is a user authentication system and then some public user profile information saved in a user's collection in Firestore. That's pretty straightforward, but you have a lot of different options when it comes to modeling the relationship between users, chats, and individual messages. I think the logical first approach that most people would take is to set up a collection of chats, and then for each of these chats, create a sub collection of messages. This is a good way to go because you can save all of your Chat Metadata on one small document and then query as many messages as you need from the Sub Collection. Action. And as an alternative, you might make this a RootCollection, so you can query the messages across all of the different chats throughout your application. For example, a RootCollection would allow you to query all the messages for a specific user across all of the different chats that they're participating in. I think this approach is the most flexible, but the drawback is that you need to execute a document read for every message that you show in the UI. Eye. In this video, we're going to take a completely different approach by modeling each Chat as its own document and then embedding the messages for that Chat on the document. This method is fast and simple, but there are some limitations. 1st, we can no longer query the individual messages because they're embedded on the document. We can only sort them client side. And the second thing is that documents are limited to 1 megabyte of data, which is conservatively somewhere between 250 and a 1000 messages per document. To address this limitation, we'll set up a Cloud function that will either archive or delete older messages. And this has the added benefit of keeping the document size relatively small, so the initial load should always be very performant. If we look at the actual database, you can see that each chat has an owner and then each individual message is an object inside this messages array. It contains the user ID, a created at timestamp and the message content. When a user is logged in and they create a new chat, it will create this document with their user ID as the owner ID. This will give us a URL that the user can then share, so other authenticated users can use this link and then catered users can use this link and then participate in the chat. Currently, all the chats are public, but check out Episode 75 if you want to learn more about user authorization in Firestore. App. Now, let's get to building this thing. The first thing you'll need is an Angular app with AngularFire and Firebase installed. I'm not going to cover every single line code in detail, but you can find the full source code on angularfirebase.com. We have 3 main resources in our code that we'll focus on. The first one is our authentication service which gets a user logged in and saves their information to Firestore. Then we have a chat service which handles all the interaction with the chat like creating new ones and adding messages to it. And then we have a Chat component which handles displaying the actual chat information in the UI. The Chat component will be loaded by the Angular router and you can see here that it's nested under a dynamic ID, so we can save each chat under its own unique URL. The first thing we'll look at is the auth service and we're not going to go through it line by line, but I just want to show you generally what's going on. You can get a more detailed explanation in Episode 55. The first thing we're doing is we're listening to the AngularFire auth state, which is the currently authenticated user. And then we're going into Firestore and grabbing a related user document with their profile information. Then, we also have a handful of methods to sign the user in with Google and also sign out. And the one unique line of code that you'll see throughout App's video is this GetUser method, which converts the user observable to a Promise, so we can later use it with AsyncAwait. This is just much easier to work with when it comes to writing data to Firestore. So now that we have a way to manage user data, we're going to go into our Chat Service and build out the actual methods that control our chat. The service will use AngularFirestore along with our auth service, the router and will also import Firestore directly from Firebase app. The first method we'll write is git chat which just retrieves a chat document from the Firestore database. We're going to want the chat document to be retrieved as an observable because it will constantly changing. And we'll also wanna tell AngularFire to map the document ID along with the data payload as the final result from this observable. This will just make it easier to pass the document ID around once we get to the main component here in a few minutes. The next method create will add a new document to the database. We can make it an async function because all write methods in Firebase are Promise based. When we fire this create method, we want to ensure that we have the currently authenticated user's user ID. We can get it by awaiting the Promise and then we'll set up a data payload here with the user ID, a timestamp and then also an empty array of messages. From there, it's just a matter of making a reference to the Chats Collection and then we'll call the add method to add this data as a new document. When that operation is complete, we'll go ahead and use the Angular router to navigate to the URL with that chat ID. That takes care of creating a new chat room, adding messages to the chat follows a very similar process. We'll once again create an async function that takes the Chat ID and the message content as an argument. Then we'll determine the current user's ID with our getUser method, and then we'll set up a data payload with the user ID, the content, and a created at timestamp. We're going to use a relatively new method in Firestore, which is the ArrayUnion method. It changes the behavior of updates when working with Arrays. So if we have a unique object, it's going to append that object to the end of the Array. The nice thing about this is that it ensures every message is unique and item potent. In other words, if we accidentally submit the same data payload twice, only one of those objects will actually be added to the chat array. And that's actually a really nice advantage over working with a dedicated collection for your messages. The last thing I'm gonna show you here requires some pretty advanced RxJS knowledge. If you get completely lost with this code, it's understandable and I recommend that you watch my Firestore Joins video to get a better idea of what's going on here. So the purpose of this method app is to join the user profiles to the individual chat messages. This will ensure that our chat messages always show the most recent user profile Image and Username on the message itself. This can get pretty complex, because a user can post multiple messages to a single chat room, but we don't want to redundantly we read their profile data over and over again. So what we do is we take the original chat document and find all of the unique user IDs within that messages array. Then, we read all those user profiles concurrently using the combineLatest method. And lastly, we recompose the original messages object, so that every message also has a user property with the user profile data. The end result is that we have a single object that has all the chat messages and all the user profiles ALs and everything is synced up in real time. So that takes care of most of the hard stuff. Now, we can get into the Chat component and build out the UI. This component will depend on our chat service and also activated route from the Angular router. We have 2 properties on this component. We have the ChatObservable, which is the actual data that we want to show in the UI and then New Message, which is the form input that the user will enter to add a new message to the chat. Now, because this component is loaded by the Angular router, we can grab the Document ID using ActivatedRoute, which we do by saying route Snapshot Param Map Git ID. We can use that ID to first set up a source observable of the chat messages. And you could just stop at this point if you only need the messages, but we also want to join the users to this original Observable. So we'll set up the Chat property as the observable returned from the JoinUser's method. For this demo, I'm just going to use the most basic type of form that you can use in Angular, which is an ngModelForm. These are also called template driven forms and, basically, we're just going to take the value from a Form input and send it to our SendMessage method, which will update Firestore. App Store. And the last little tip here is that whenever working with a Real Time Array and looping over it in Angular, you should set up a TrackBy method. This just tells Angular to only rerender items that have changed and it will dramatically improve the user experience and performance. Now we can switch over to the HTML and we'll set up an ng if statement that unwraps that Chat Observable. You'll notice I'm doing this on an NG container, which allows us to set up a context in this template without actually rendering any DOM elements. So once our Chat observable data is ready, we can just use it as this chat variable throughout the template. From there, I'll set up an ngForLoop and loop over the messages in the ChatMessages array. Because they're all embedded on the document, we don't have to wait for anything asynchronous. And this is the point where we'll also wanna use our trackBy function. From there, we'll have access to the message itself plus some information about the user. And the final step is to set up our form. If we use app ng model, it will bind this new message property to the component typescript. And then if the user clicks the enter key, we'll go ahead and submit their chat message. App. And just for good measure, we'll also add a submit button for users that don't have an enter key. That takes care of the front end code, but there's still one thing that we have to address to really make this a usable feature. As I mentioned earlier, this data model will only work up to 1 megabyte, at which point Firestore will start rejecting new messages. We can actually address this with a very simple Cloud function that just listens to the incoming messages. And if we get over a certain point, it will just either archive or delete the oldest messages. If you look over here on the left, I'm adding new messages. And once I get over 10, it's going to start deleting messages on the right. That's my Cloud Function working in the background, and I don't know if something changed recently, but there's almost no latency on these requests. The Cloud Function round trip finishes in just a few 100 milliseconds. So I already have Cloud Functions initialized in this project with TypeScript, and the only thing I'm doing is importing the Admin SDK and initializing it. Then we'll go ahead and define Firestore as our database. Then we can name our function archiveChat. Then we'll have it point to our chat collection and only run when the document is created. The purpose of this function is to look at the current size of the messages array. If there's more than 50 messages in there or if the messages characters are greater than 10,000, then we'll want to start deleting or archiving those messages. It's not actually implemented here, but you could use JSON stringify to check the total length of the object as a string. Then you can roughly estimate the total size of the document based on the length of that string. So if we determine that the array is too big, then we'll go ahead and start splicing items from the beginning of that array or the oldest messages. Then you'll also notice that I'm setting this up as a Batch Right, because you might want to add the deleted messages to some other location. If that is the case, you definitely wanna is an atomic batch because you don't want the delete operation to succeed and the update to fail. Then you would lose those messages forever. And, lastly, we'll just go ahead and return null. If these conditions are not met, we won't make any update to the existing document. Now we have a basic chat feature up and running. Make sure to check out the link in the description and play around with the live demo based on this code. If this video helped you, please like and subscribe, and I realized that we went through that code pretty fast, but I'm working on a fully fledged chat feature for the Ionic course, which will be released in the next week or 2. So if you want to see more features at a slightly slower pace, then I recommend upgrading to a Pro membership to get access to that course. Thanks for watching and I'll talk to you soon. App,
        