Support chat with Angular + Firebase Realtime Database

Angular + Firebase Realtime Database

Hey folks, hope you’re all doing good. Recently, while working on a project, I got a requirement for a chat service, but this time, using firebase realtime database and not Twilio or Intercom. Here’s the exact requirement —

  1. There is a customer end of chat where he has the ability to chat with one agent at a time.
  2. There is an agent view where all the agents will log in to and must have the ability to view all messages from all customers.
  3. The ability to handle and show “new messages” from a customer to an agent.
  4. When an agent starts chatting with a customer, display an overlay on the customer name with the agent’s name indicating that this chat is in progress.

Before we start, I will not include styles here, the template I include is only to share certain points (and also probably hoping someone more proficient would help me clean up a bit and optimise the code if any). That being said, The styles and display is totally up to you to play around with.

Let us start by creating the firebase chat model first. Here’s how the tree flows. We need to get the list of users that have had a chat conversation, so the key of storing the chat is not to let firebase define a unique key, but to store all the chat with our user’s firebase IDs. Under each user, we have two objects — “messages” and “meta-data”. As the name indicates, the messages hold the actual message data and the meta-data is designed to hold the information of the user, agent and if the latest message is read.

The “messages” object holds four keys — “senderName”, “messageBody”, “timeStamp” and “senderID(which is again the firebase_id)”. The meta-data has two objects, one for the user and the other for the agent with “name” and “new”. Here’s a sample screenshot

The message structure (this is one message)

Now that the database model is ready, It is time to integrate it with Angular. In my case though the requirement was a little different, as in I had to take the list of firebase IDs and query my backend to fetch user details. If you are using firebase itself to store all user information, you could skip that step and directly get the user information. We will discuss this when the time comes. For now, it’s time to create a new Angular 7 app and install the @angular/fire module.

ng new support-chat
npm i @angular/fire — save

Ok, I will assume that your firebase setup is completely ready with users and everything setup (including the above mentioned database model). That said, here’s what we need

app.module.tsimport { AngularFireModule } from ‘@angular/fire’;
import { AngularFireDatabaseModule } from ‘@angular/fire/database’;
imports: [
AngularFireModule.initializeApp(environment.firebaseConfig),
AngularFireDatabaseModule,

]

Note: You will get your firebaseConfig when you create an app in the firebase console. I stored it in the env file.

Great! Now to create the chat component and a chat service.

ng g c components/chat
ng g s components/chat/chat

Let’s first look at integrating Firebase, we can talk about the component later. First thing first, we will import angular firebase database, reference our firebase db and start adding / reading messages to and fro Firebase.

chat.service.tsimport { Injectable, OnInit } from '@angular/core';import { AngularFireDatabase, AngularFireObject } from '@angular/fire/database';

Now that we imported AngularFireDatabase, let’s add it to our constructor

chat.service.tsconstructor(private db: AngularFireDatabase){}

There are 2 methods to query for messages list. One is to create a ref and move deep into the children and the second is to directly list with respect to a path. I chose to go with the second option and hence the code below to fetch a list of all messages directly under the Chat “table”.

chat.service.tsgetMessagesList() {  
return this.db.object('Chat').valueChanges();
}

To explain this, when you use db.object, you get an array with the “key” field as the user’s “firebase_id”. We can use this to get an array of firebase IDs. Like I said earlier, in my case, I had to get the user’s details from my backend since there were a few data missing in the firebase user database. Remember you can skip this and directly get your user details directly from firebase. Now to extract the firebase_ids from the result — We will add this piece

chat.component.tsthis.chatService.getMessagesList().subscribe(messagesList => {
this.messagesList = messagesList;
this.usersList = Object.keys(this.messagesList).map(val => {
return val;
});
});

Now, this.usersList consists of an array of firebase IDs which I can send to my backend to fetch all the users’ details. Let us store the final result with the variable name searchableUsersList Once I have this data, I can use it to display the list for the agent in the template — Your chat.component.html

chat.component.html<div class="position-relative"
*ngFor="let user of searchableList"
(click)="showChat(user.firebaseId, $event);
getChat(user.firebaseId)"
>
<div class="overlay"
*ngIf="messagesList[user.firebaseId]['meta-data']?.agent && !
(messagesList[user.firebaseId]['meta-data']?.agent?.name == '')"

>
<p class="overlay-text">{{ messagesList[user.firebaseId]['meta
-data']?.agent?.name }} is typing...</p>
</div>
<div class="messages-section row cp m-0 px-2">
<div
class="col-2 px-0 prof-img"
[ngStyle]="{
background: 'url(' + user?.profileImage ||
'../../../../assets/images/user.svg' + ')',
'background-position': 'center',
'background-size': 'cover'
}"
></div>
<div class="col-10 align-self-center">
<p class="name">{{ user?.name }}</p>
<p class="message" [ngClass]="messagesList[user.firebaseId] ['meta-data']?.user.new ? 'bold-text' : ''">
{{ getLatestMessage(messagesList[user.firebaseId]) }}
</p>
<p class="message text-right">
{{ getLatestMessageTime(messagesList[user.firebaseId]) | timeAgo }}
</p>
</div>
</div>

Keep an eye out for the bold lines as there isn’t much apart from those to be explained here. As you can see in the above code, the click function invokes a showChat() and a getChat() method. Since this list is only a ref, we will have to make an API call to get only one chat object from firebase every time a user item is clicked upon. The ngIf statement on the overlay activates the overlay only when the Agent has started conversing with the customer. The ngClass checks if the message posted is new with respect to our meta-data and applies a bold text, if true indicating that the message is new. The two template-invoked functions are to fetch the messageBody and the timeStamp of the latest message (I couldn’t find a better way, if you can tell me something better, I’m all ears).

Now that we have successfully displayed the left portion of our support app that consists of the list of users that have already initiated a chat with us in the past, let us look at getting and showing the individual chat messages. So we use the getMessages(firebase_id) to get the messages and here’s how to set the path in the service file —

chat.service.tsgetMessages(user) {
return this.db
.list('Chat/' + user + '/messages', ref => {
return ref.orderByChild('timeStamp');
})
.valueChanges();
}

Subscribing to this api request in your component.ts file will give you the complete chat history as an array and you can render them in the same format as above. Your template will depend on the information you want to display so I will leave it to you. This completes displaying all the information on your template.

The last 2 sections we need to look at is creating a new message and updating the values to display read and unread messages properly and to end the chat removing the overlay on the selected user.

To create a new message, all the information you would require is the message typed. You can get the customer’s firebase_id storing the selected one’s firebase_id in a variable (this is the same ID we used to get the chat messages). So in your component —

chat.component.tspostMessage() {
const user = {
id: this.agent._id,
name: this.agent.name
};
this.chatService.sendMessage(user, this.chatMessage, this.selectedUser);
}

The user parameter under the sendMessage function is our chat agent and the selectedUser is the firebase_id (also the chat_id in our case) where we need to update the data. And the second part of actually updating the database —

chat.service.tssendMessage(user, message, chatID) {
const messageData = {
senderID: user.id,
messageBody: message,
senderName: user.name,
timeStamp: new Date().getTime()
};
const agentMeta = {
name: user.name,
new: true
};
const userMeta = {
new: false
};
this.db.list(`Chat/${chatID}/messages`).push(messageData);
this.db.database.ref(`Chat/${chatID}/meta-data/agent`).update(agentMeta);
this.db.database.ref(`Chat/${chatID}/meta-data/user`).update(userMeta);
}

The first part of this function is all data formation, let’s not bother about that since we already know what we need to do. The first db action we do is push the messages to the array list in the database, which will create another user object with messages. Now to add the metadata that says the customer’s message is read and an agent has been assigned to the customer; and this precisely what we are doing with the db ref update method. Notice the update; this is because from the second time on, we do not want to recreate, when it already exists, we just update it.

To the last part that is to end the chat from the agent’s end — this is to tell the other agents / manager that he is done with the chat and if the user chooses to start a conversation again, any free agent can start the chat again.

chat.component.tsendConversation() {
this.chatService.endConversation(this.selectedUser);
}

All we need to do is update the metadata to remove the agent’s name and set the messages’ new property to false. The corresponding service —

chat.service.tsendConversation(chatID) {
const agentMeta = {
name: '',
new: false
};
const userMeta = {
new: false
};
this.db.database.ref(`Chat/${chatID}/meta-data/agent`).update(agentMeta);
this.db.database.ref(`Chat/${chatID}/meta-data/user`).update(userMeta);
}

And that’s all folks!

You can find a gist containing both the typescript files here.

Next up, I am going to write about connecting Twilio Call feature to this chat component that we just built. Please feel free to provide constructive criticism on this post as it help me learn better as well. Looking forward to hearing from you guys!

It was awesome to type this out and share it with ya’ll. This is my first tech post, feel free to point out mistakes and correct me wherever you think I’m wrong. Open to constructive criticism! Thank you for reading through and have a wonderful day/evening. CHEERS!

ReactJS, NextJS, NodeJS and every other JS! It’s like a never ending learning journey. 😎

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store