This is a final and last part of the Building Real time private chatting app using Angular. In this part, we will finish our application by adding the features listed below,
- If any user is selected from the chat list than we will fetch the conversation between users.
- We will add the real-time chat feature in this application.
- We will add the Logout feature.
A little recap again, To integrate the real-time morality in our application, we connected our Angular application to Socket Server. Later we implemented the Realtime chat list for the application. On the server side, we wrote Socket Server Event to Emit the updated Chat List. And at the end, we Highlighted the select user from the list of users.
The last thing we did in thethird part was highlighting a selected user from the chat list to start a new chat. We will start our implementation from where we left in the last part. So let’s start the implementation,
Fetching the conversation between users
As the title reads Fetching the conversation between users, it seems very easy, right! All you have to do is make an HTTP call and you are done. Well, this is exactly what you are going to do but there is a little problem.
ChatListComponent
is a separate component and the ConversationComponent
will be a separate Component, which we will create in next few minutes. So here you have to pass data between two sibling component.
Also read,Passing data between Angular 2 Components
So first we will create Conversation
Component and then we will display messages and do other things. As always I will divide this section’s explanation into smaller chunks and this way we will speed the development of this application.
Creating Conversation Component
Let’s createConversation
Component nothing so special about it. Execute the below Angular CLI commands.
The First command to create Conversation
Module,
ng g module conversation
Then the second Will generate the Actual Component,
ng g component conversation
Now your Conversation
component is ready to use, So why wait let’s import it intoHome
component and see if it works!
Open thehome.module.tsand add the below line,
home.module.ts:
/* * Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { UISupportModule } from './../../modules/ui-support/ui-support.module'; import { HomeRoutingModule } from './home-routing.module'; import { HomeComponent } from './home.component'; import { ConversationModule } from './conversation/conversation.module'; import { ChatListModule } from './chat-list/chat-list.module'; @NgModule({ imports: [ CommonModule, HomeRoutingModule, UISupportModule, ConversationModule, ChatListModule ], declarations: [HomeComponent] }) export class HomeModule { }
Now you can add the<app-conversation>
into your home.component.html file, and it should show the traditionalconversation works!
message. So at this point, yourhome.component.htmlfile should look like this.
home.component.html:
<!-- Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io @author Shashank Tiwari --> <!-- Loading overlay section starts --> <div *ngIf="overlayDisplay" class="overlay"> <h1>Loading</h1> </div> <!-- Loading overlay section starts --> <!-- header section starts --> <header class="app-header"> <nav class="navbar navbar-expand-md"> <a class="navbar-brand" href="#">Hello {{username}}</a> </nav> <ul class="nav justify-content-end"> <li class="nav-item"> <a class="nav-link active" href="#" (click)="logout()">Logout</a> </li> </ul> </header> <!-- header section ends --> <main role="main" class="container content" *ngIf="!overlayDisplay"> <div class="row chat-content"> <div class="col-3 chat-list-container"> <app-chat-list></app-chat-list> </div> <div class="col-8 message-container"> <app-conversation></app-conversation> </div> </div> </main>
Send data of the selected user from the chat list to the conversation Component
It’s a general thinking that; when you click on any user from the chat list you would like to see the conversation between you and that user. In this section, I will explain how to pass the data from the Chatlist
component to the Conversation
component.
=> Since you have already written complete chat list code in the last part, hence here you don’t have to write anything.
=> Open the chat-list.component.ts and you will find selectedUser()
method.
chat-list.component.ts:
/* * Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */ selectedUser(user: User): void { this.selectedUserId = user.id; this.dataShareService.changeSelectedUser(user); }
Explanation:
- The above code is very easy to understand, in the first line we have assigned the user id of the selected user from the chat list to the
selectedUserId
property. - The second line is kinda interesting for us since it would emit the data to the other component.
- Yes, by using
DataShareService
we will push the user parameter and that we will read inside theConversationComponent
class.
In the below section we will catch the data sent from the Chatlist
component.
Receiving incoming data in Conversation component from ChatList component
This part can be a pain for newbie developers, but it shouldn’t be actually. We can create a service and which will just share the data between different component.
As of now in this I won’t go into details, we will just write down the code for our Component classes. Again read this article,Passing data between Angular 2 Componentsand understand how to pass data between components.
=> In this application, I amRxJS BehaviorSubjectand I have added this service to our boilerplate. So you don’t have to worry about that, just worry about component classes and HTML.
=>Open theconversation.component.ts
and add the below code,
conversation.component.ts:
/* * Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */ ngOnInit() { this.userId = this.dataShareService.getUserId(); this.dataShareService.selectedUser.subscribe( (selectedUser: User) => { if (selectedUser !== null) { this.selectedUser = selectedUser; this.getMessages(this.selectedUser.id); } }); }
Explanation:
In the above code,
- First, we are fetching the
userid
using datashare service. - Since we are using
BehaviorSubject
, hence we have subscribedselectedUser
property. - We will fetch the conversation between users, whenever
selectedUser
property will emit the new user id. - We will fetch messages using
getMessages()
method, we will talk more about this method later.
Fetching the conversation between users from the server
In this section, we will make an HTTP call to fetch the conversation between the user from the server. So to do this we will use thegetMessages()
method. This method is defined inside the ConversationService
class.
So open the conversation.component.ts and write down the below code. But first, add the below code properties inside the ConversationComponent
class.
conversation.component.ts:
public messages: Messages[] = []; public messageLoading = true;
Now add the below code in the ConversationComponent
class,
conversation.component.ts:
/* * Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */ getMessages(toUserId: string) { this.messageLoading = true; this.chatService.getMessages({ userId: this.userId, toUserId: toUserId }).subscribe((response: MessagesResponse) => { this.messages = response.messages; this.messageLoading = false; }); }
Explanation:
- The method
getMessages()
requiresuserId
(logged in user) and thetoUserId
(user selected from the chat list). - Once we receive the response from the server, we will render messages using
messages
property.
Let’s write a Nodejs express Route to return the response when we try to fetch the response from/getMessages
route. Open the routes.js and add the below route to it,
routes.js:
this.app.post('/getMessages', routeHandler.getMessagesRouteHandler);
As you can see, we have calledgetMessagesRouteHandler()
method to handle the request and response for this route. So as always, it will send an appropriate response based on the result from the database.
Open the route-handler.js and the below method into it,
route-handler.js:
/* * Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */async getMessagesRouteHandler(request, response){ let userId = request.body.userId; let toUserId = request.body.toUserId; if (userId == '') { response.status(CONSTANTS.SERVER_ERROR_HTTP_CODE).json({ error : true, message : CONSTANTS.USERID_NOT_FOUND }); }else{ try { const messagesResponse = await queryHandler.getMessages({ userId:userId, toUserId: toUserId }); response.status(CONSTANTS.SERVER_OK_HTTP_CODE).json({ error : false, messages : messagesResponse }); } catch ( error ){ response.status(CONSTANTS.SERVER_NOT_ALLOWED_HTTP_CODE).json({ error : true, messages : CONSTANTS.USER_NOT_LOGGED_IN }); } } }
Explanation:
- First, we validated the request parameters and based on that server returns the response.
- Then inside the else block, we fetch the messages from MongoDB by calling
getMessages()
method. - If all goes as expected then we return a response to messages.
Now let’s fetch data from database using MongoDB query. Here I expect from you that you are familiar with MongoDB queries.
Open the query-handler.js file and add the below method, this method will basically fetch the messages from the MongoDB.
query-handler.js:
/* * Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */getMessages({userId, toUserId}){ const data = { '$or' : [ { '$and': [ { 'toUserId': userId },{ 'fromUserId': toUserId } ] },{ '$and': [ { 'toUserId': toUserId }, { 'fromUserId': userId } ] }, ] }; return new Promise( async (resolve, reject) => { try { const [DB,ObjectID] = await this.Mongodb.onConnect(); DB.collection('messages').find(data).sort({'timestamp':1}).toArray( (err, result) => { DB.close(); if( err ){ reject(err); } resolve(result); }); } catch (error) { reject(error) } }); }
Explanation:
- The first and most important thing here is the MongoDB query. It’s a combination of or and operators, which will basically give the required result.
- And then we return a Promise, which resolves a query result.
And that’s it.
Rendering the conversation as a chat messages
At this point, you have all the messages between two users in messages
property. The only thing is remaining now, is writing markup so that you can render messages.
Now open theconversation.component.html and the below markup into it,
conversation.component.html:
<!-- Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io @author Shashank Tiwari --> <div *ngIf='messageLoading' class="message-overlay"> <h3> {{selectedUser !== null && selectedUser.username ? 'Loading Messages' : ' Select a User to chat.' }}</h3> </div> <div class="massege-wrapper" *ngIf='!messageLoading'> <div class="massege-container"> <div class="opposite-user" *ngIf="selectedUser !== null && selectedUser.username"> Chatting with {{selectedUser.username}} </div> <ul #messageThread class="message-thread" *ngIf="messages.length !== 0"> <li *ngFor="let message of messages" [class.align-right]="alignMessage(message.toUserId)"> {{ message.message}} </li> </ul> <div class="message-thread start-chatting-banner" *ngIf="messages.length === 0"> <p class="heading"> You haven't chatted with {{selectedUser.username}} in a while, <span class="sub-heading">Say Hi.</span> </p> </div> </div> <div class="message-typer"> <!-- Markup to send messages goes here --> </div> </div>
Explanation:
- First, there will be an overlay on the Conversation Component, till you select any user from the chat list.
- Once you will select a user from the chat list, then we will show a message saying that; You are chatting with someone, for example, Chatting with Shashank
- Then we have
*ngFor
to render the messages using messages property from theConversationComponent
class. alignMessage()
method will align the messages to left or right based on the user id of the user.
Now if you look at your screen you should see something similar to the below image,
Chat between two users
Sending and Receiving Real-time messages
Finally! The last and the final puzzle of the application, here first we will send a message to the selected user from the chat list. And then, we will write a code to listen for the incoming messages from the server.
Since this section is a little long, hence we will divide this section into two parts. In the first part, we will write code to send the messages. In the second part, we will write code to receive the messages.
Sending real-time messages
So let’s start with the markup open the conversation.component.html and write the below code beneath thediv
tag having.message-typer
class.
Here we don’t have too much to understand except keyup Event. In the keyup event, we will send the message to the server when the key code will be13
.
In less technical term when user will press enter button we will send the message to the server.
conversation.component.html:
<!-- Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io @author Shashank Tiwari --> <div class="message-typer"> <form [formGroup]="messageForm" novalidate> <textarea class="message form-control" placeholder="Type and hit Enter" (keyup)="sendMessage($event)" formControlName="message"> </textarea> </form> </div>
=> The messageForm
property will hold the instance of the form.
=>We have defined the sendMessage()
which will send the message to the server.
So open the conversation.component.tsfile and add the below method,
conversation.componet.ts:
/* * Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */ sendMessage(event) { if (event.keyCode === 13) { const message = this.messageForm.controls['message'].value.trim(); if (message === '' || message === undefined || message === null) { alert(`Message can't be empty.`); } else if (this.userId === '') { this.router.navigate(['/']); } else if (this.selectedUser.id === '') { alert(`Select a user to chat.`); } else { this.sendAndUpdateMessages({ fromUserId: this.userId, message: message, toUserId: this.selectedUser.id, }); } } }
Explanation:
- First, we will check for the validation. If anything goes wrong with that, then we will show the alert.
- After that we inside
else
block, We have called another methodsendAndUpdateMessages()
. - Also, we will push this data into existing
messages
array. - Next, we actually send this data to Nodejs server by calling
sendMessage()
method, Which basically emits the socket event. - And at end, we scroll the scrollbar to the bottom.
Now let’s addsendAndUpdateMessages()
method, inside theConversationComponent
class.
conversation.componet.ts:
/* * Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */ sendAndUpdateMessages(message: Message) { try { this.messageForm.disable(); this.socketService.sendMessage(message); this.messages = [...this.messages, message]; this.messageForm.reset(); this.messageForm.enable(); this.scrollMessageContainer(); } catch (error) { console.warn(error); alert(`Can't send your message`); } }
Explanation:
- Here we will disable and enable the
messageForm
instance, while Angular is emitting the socket event. - After that, we actually send this data to Nodejs server by calling
sendMessage()
method, which emits the socket event. - Also, we will push this data into existing
messages
array. - And at the end, we scroll the scrollbar to the bottom.
All this work that we just did; is incomplete without writing the nodejs socket event. And also we need to save these messages in MongoDB.
The below socket event will listen to the socket event and based on the user’s id it will send messages to the recipients. Since it’s a socket event that we are emitting from Angular, So open the socket.js and ad the below code,
socket.js:
/* * Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io * @author Shashank Tiwari * * send the messages to the user */socket.on(`add-message`, async (data) => { if (data.message === '') { this.io.to(socket.id).emit(`add-message-response`,{ error : true, message: CONSTANTS.MESSAGE_NOT_FOUND }); }else if(data.fromUserId === ''){ this.io.to(socket.id).emit(`add-message-response`,{ error : true, message: CONSTANTS.SERVER_ERROR_MESSAGE }); }else if(data.toUserId === ''){ this.io.to(socket.id).emit(`add-message-response`,{ error : true, message: CONSTANTS.SELECT_USER }); }else{ try{ const [toSocketId, messageResult ] = await Promise.all([ queryHandler.getUserInfo({ userId: data.toUserId, socketId: true }), queryHandler.insertMessages(data) ]); this.io.to(toSocketId).emit(`add-message-response`,data); } catch (error) { this.io.to(socket.id).emit(`add-message-response`,{ error : true, message : CONSTANTS.MESSAGE_STORE_ERROR }); } } });
Explanation:
- The first thing would be validation, check if everything is okay. If all goes well then we will go ahead and do the further operation.
- Inside the else block, we are calling two methods
getUserInfo()
andinsertMessages()
; both these methods are defined inside theQueryHandler
class. getUserInfo()
method will fetch socket id of the user who is intended to receive a message and theinsertMessages()
method as the name suggests will store the messages into a database.- And at the end, we will emit an event with the data and this data is nothing but the data received in the
add-message
event.
Receiving Real-time messages
This part is damn so easy to implement. Here we will create a method to listen for the incoming socket events. After receiving the socket event, we will push the new messages in the messages
array.
=>So I have createdlistenForMessages()
method inside the ConversationComponent
class.
=> We will call this method from thengOnInit()
method.
=> Once you call this method, it will listen to the incoming messages and automatically push the new messages in the messages
array.
So open the conversation.compotent.ts and add the below inside the ngOnInit()
method,
conversation.compotent.ts:
/* Calling Compoenent method to Listen for Incoming Messages*/this.listenForMessages();
Now createlistenForMessages()
method and add the below code to it,
conversation.component.ts:
/* * Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */ listenForMessages(): void { this.socketService.receiveMessages().subscribe((socketResponse: Message) => { if (this.selectedUser !== null && this.selectedUser.id === socketResponse.fromUserId) { this.messages = [...this.messages, socketResponse]; this.scrollMessageContainer(); } }); }
Small tip, in case, if you want to show notifications. For example, you received a new message from Shashank.
You can do that here.
Explanation:
- Here we will call
receiveMessages()
method which defined inside the SocketService class. - Once you receive a new message, you will add that message into the
this.messages
property of theConversationComponent
class. - And then again, we will move the scroll bar to bottom as a tradition.
Implementing Logout
Implementing Logout, yeah that the last thing is left to do. So here we will basically work only in HomeComponent
Class. I really don’t need to give an overview of Logout functionality.
So let’s just hop into home.component.html add the below markup, and make sure your header looks like below markup,
home.component.html:
<!-- Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io @author Shashank Tiwari --> <!-- header section starts --> <header class="app-header"> <nav class="navbar navbar-expand-md"> <a class="navbar-brand" href="#">Hello {{username}}</a> </nav> <ul class="nav justify-content-end"> <li class="nav-item"> <a class="nav-link active" href="#" (click)="logout()">Logout</a> </li> </ul> </header> <!-- header section ends -->
Now open the home.component.ts and the below code. In this below, we will consume the SocketService
to achieve this functionality.
home.component.ts:
/* * Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */logout() { this.chatService.removeLS() .then((removedLs: boolean) => { this.socketService.logout({ userId: this.userId }).subscribe((response: Auth) => { this.router.navigate(['/']); }); }) .catch((error: Error) => { alert(' This App is Broken, we are working on it. try after some time.'); throw error; }); }
Explanation:
- Inside the
logout()
method, we calledremoveLS()
method, which is defined inside theChatService
class. This method will delete the all the data stored in local storage related to this application. - Then After that, we have called Logout method from
SocketService
class. This method will send a socket event to the server. This will change the online status to offline.
Now the last part of the puzzle is socket event that will change the status from online to offline. So open the socket.js and add the below event to it,
socket.js:
/* * Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */socket.on('logout', async (data)=>{ try{ const userId = data.userId; await queryHandler.logout(userId); this.io.to(socket.id).emit(`logout-response`,{ error : false, message: CONSTANTS.USER_LOGGED_OUT, userId: userId }); socket.broadcast.emit(`chat-list-response`,{ error : false , userDisconnected : true , userid : userId }); } catch (error) { this.io.to(socket.id).emit(`logout-response`,{ error : true, message: CONSTANTS.SERVER_ERROR_MESSAGE, userId: userId }); } });
Explanation:
- The below line will modify the online status of the user by making a query into MongoDB database.
await queryHandler.logout(userId);
- After doing that this event will emit the information of the logged out user in order to update the chat list.
- And on the front end, Angular will change the online status of the user.
Final thoughts
If you are still with me from the start and you followed each and every step, then pat yourself on your back.
This series was too big, but I thought it was worth writing it.
But the good thing is now you know, how to create a private chatting application in Angular 2 with minimum complexity, for now, that’s the end of this series. Use both theAngular boilerplateandNodejs boilerplate,I highly recommend it. If boilerplates don’t work for you, then clone the source code from GitHub for Angular as well as for Nodejs server and try to relate to each point.
If you have any suggestion, Question or feature request, let me know in the below comment box, I would be happy to help. If you like this article, do spread a word about it and share with others.
Hello Shashank Tiwari, it’s really a good work and it gives me a lot of inspiration. I would thank you so much for this work. However, I suggest to implement the concept of Room and namespaces in your current application.
And thank you a lot.
First of all thanks for reading this post, Sure I will consider your suggestion.
I think you are very brilliant. Thank for your tutorial.
I learn a lot of knowledge from your post.
Such a wonderful Shashank.
Hi,
Thank you for the good words.
hello great tutorial one question you do not have this app with angular 4? it gives me errors in boostrap and I do not find how to solve it
Can you post your errors here ?
Good day! Thank you very much for your tutorial!!!
But one moment only, I’m getting an error : “Error: ENOENT: no such file or directory, stat ‘./../Nodejs/src/index.html'” when I’m starting a server side (localhost:4000). I think that a reason is in routes.js:
…
this.app.get(‘*’,(request,response) =>{
response.sendFile(path.join(__dirname,’../dist/index.html’));
});
…
May be you can help me! :))
Hi Lana,
You can create index.html file and show some nice UI on the screen.
Or you can write below code, it will return the json reponse.
Also, in next 3 to 4 days I’ll update this article, which has log of best practices including in Angular as well as in Nodejs.
Thanks.
Thank you! You are super!
Good day! Sorry for my question. But your opinion is a very important for me. I have an API on PHP. Do you think it is possible to connect your application (chat on mongoDB) to my API (DB on a postgresQL)? Thank you for your time!
How are you handling the online and offline status when the browser is closed ? Can you please explain me what part of the code is handling the online to be ‘N’ when the browser is closed without logging out ?
Hi sri,
When we close the browser tab or refresh the page the socket connection gets terminated. You can execute logout query here, which I won’t recommend.
As of now this code handles the logout when you close browser tab, or you refresh your tab.
Though it’s not accurate at all,
There are few scenarios on which am working on without applying any dirty hack, Such as
1. Unexpected process termination.
2. Executing
Logout
event when tab is closed.ASA I’ll find something, I’ll update the article.
I want to chat between two users like skype chat
Can we use our code in Angular5 version?
Yes, you can. If something doesn’t work let me know here, I’ll happy to help.
Thanks a lot ! Socket.io always confused me a little bit. This illustrates its use and power in a clear way. Very good job.
Good day!. Shashank Thanks for such a nice tutorial, but I have a problem about nodeserver and cant figure out,
Can you take a look and help me please?
HttpErrorResponse {headers: HttpHeaders, status: 412, statusText: “Precondition Failed”, url: “http://localhost:8080/usernameAvailable”, ok: false, …}
error: {error: true, message: “Something bad happend. It’s not you, it’s me.”}
headers: HttpHeaders {normalizedNames: Map(0), lazyUpdate: null, lazyInit: ƒ}
message: “Http failure response for http://localhost:8080/usernameAvailable: 412 Precondition Failed”
name: “HttpErrorResponse”
ok: false
status: 412
statusText: “Precondition Failed”
url: “http://localhost:8080/usernameAvailable”
__proto__: HttpResponseBase
have a nice day.
Hi Alen, This error occurs when you send an invalid request to the server. Check the HTTP request parameter or HTTP header.
Thanks and let me know if I was helpful.