This is the third part of the Building Real time private chatting app using Angular. In this part, you will implement below-listed features in your application,
- You will connect your Angular application to Socket Server.
- You will implement the Realtime chat list for the application.
- You will write Socket Server Event to Emit the updated Chat List.
- Highlight the select user from the list of users
Before Going any further a little recap,
In the previous part of the series, we completed the Login/Registration process in our application. After Login/Registration, the application will redirect the user to a home page of the application. Once you redirected to the homepage, the application will check the session of the user by making an HTTP request.
As soon as the application finishes the session checking, we will connect this Angular to the Nodejs socket server. Once socket server is connected, just after that we will subscribe to real-time chat list update.
So let’s get our hands dirty and start integrating real-time goodness into our application.
Connecting Angular application to Socket Server
We won’t just connect the Socket server by using below code, I know in most of the article you have seen, read and wrote this code to connect the Socket Server.
const socket = io('localhost:3000');
Here we will add a little extra sugar by passing a parameter while connecting to the socket server.
=>Which we will do by using the code shown below,
this.socket = io(this.BASE_URL, { query: `userId=${userId}` });
Now you may ask Why we need to this in the first place?
Well, the answer is simple. We need userId to identify the which user is just connected to socket Server. Also, we will update the socket id of the connected user in MongoDB.
=> The updated socketId will be used at the different stages of the application.
=>Since you have everything ready to go, all you have to do is call connectSocket()
(This method is defined inside the socket.service.ts file) from HomeComponent
class. Open the home.component.ts file and add the below inHomeComponent
class,
home.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.username = this.dataShareService.getUserName(); this.establishSocketConnection(); } async establishSocketConnection() { try { if (this.userId === '' || typeof this.userId === 'undefined' || this.userId === null) { this.router.navigate(['/']); } else { /* making socket connection by passing UserId. */ await this.socketService.connectSocket(this.userId); this.overlayDisplay = false; } } catch (error) { alert('Something went wrong'); } }
As I said earlier, we will update the socketId of the user in the MongoDB database, hence we can use it later in the application. If you check the socket connection string in network tab, you should see something similar to the below-shown image.
As of now, I am using the Chrome browser. Though it has nothing to do with what kind of browser you use. Anyways make sure you are passing userid while connecting socket server.
Passing user id in socket connection
Now you have provided a userId as a parameter in socket connection. Let’s complete abstract work in server side.
Open the socket.js file and add the below code to it,
socket.js:
/* * Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */socketConfig(){ this.io.use( async (socket, next) => { try { await queryHandler.addSocketId({ userId: socket.request._query['userId'], socketId: socket.id }); next(); } catch (error) { // Error console.error(error); } }); this.socketEvents(); }
Explanation:
- The first thing to notice here we are reading the userId parameter by using line shown below,
socket.request._query['userId']
- Second, we have
addSocketId()
method to update the data in MongoDB. - And at the end, we have called rest of the socket server Event by calling
this.socketEvents()
method.
Implementing a Realtime chat List
I have divided this section into the smaller sections, since covering everything into a single section would be very nosogenic to read and understand. Creating chat list involves below-listed points,
- Creating
ChatList
component. - Writing Markup for a chat list
- Writing
ChatList
componentClass
. - Writing Socket server Event.
- Updating user’s online status in
ChatList
- Selecting a User from the chat list of the user.
By the end of this article, you will have full full-fledged working chat list for your application. So let’s dig our teeth into it and finish it.
Creating ChatList component
Why a new component for ChatList? Though we can write all the markup and Code inside the HomComponent. But, It’s not a good practice and your HomeComponent class will be heavy and long to manage.
=> Therefore first things first, you have to createChatList
component nothing so special about it. Execute the below Angular CLI commands.
=> The First command to create ChatList Module,
ng g module ChatList
=> Then the second Will generate the Actual Component,
ng g component ChatList
=> Now yourChatList
module is ready to use, So let’s import theChatList
module intoHome
Module. Open the home.module.ts and add the below line and make your home.module.ts file look like this,
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 add the<app-chat-list>
into your home.component.html file, and it should show the traditional chat-list works!
message.
So at this point, your home.component.html file 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"> <!-- Component to display the conversation --> </div> </div> </main>
Writing Markup for a chat list
Open thechat-list.component.html and add the below-shown markup. The below code is ridiculously easy to understand.
=> The next thing ischatListUsers
property of ChatListComponent class.
=> InchatListUsers
property we will store the list of the users. This variable is nothing but an array, which updates itself on certain stipulated conditions.
chat-list.component.html:
<!-- Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io @author Shashank Tiwari --> <ul class="user-list"> <li *ngFor="let user of chatListUsers"> {{ user.username}} </li> </ul> <div class="alert" alert-info.class="!loading" *ngIf='chatListUsers.length === 0'> {{ loading && chatListUsers.length === 0 ? 'Loading your chat list.' : 'No User Avilable to chat.'}} </div>
Writing ChatList component Class
At this point, your ChatList component will render nothing. So to make it work we will add some code to the ChatList component class. Basically, in this class, do two important things.
=>First we will update the chat list and the second, we will pass the data of the selected user from the list of users to the Conversation component.
=>But first, let’s add the necessary properties required in ChatList component class, open the chat-list.component.ts and add the below properties,
chat-list.component.ts:
loading = true; userId: string = null; chatListUsers: User[] = [];
Now let’s add the code to update the chat list, this code will basically manipulate the chatListUsers property.
So open the chat-list.component.ts and add the below code.
chat-list.component.ts:
/* * Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */ ngOnInit() { this.loading = true; this.userId = this.dataShareService.getUserId(); this.socketService.getChatList(this.userId).subscribe((chatListResponse: ChatListResponse) => { this.renderChatList(chatListResponse); }); } renderChatList(chatListResponse: ChatListResponse): void { if (!chatListResponse.error) { if (chatListResponse.singleUser) { if (this.chatListUsers.length > 0) { this.chatListUsers = this.chatListUsers.filter(function (obj: User) { return obj.id !== chatListResponse.chatList[0].id; }); } /* Adding new online user into chat list array */ this.chatListUsers = this.chatListUsers.concat(chatListResponse.chatList); } else if (chatListResponse.userDisconnected) { const loggedOutUser = this.chatListUsers.findIndex((obj: User) => obj.id === chatListResponse.userid); if (loggedOutUser >= 0) { this.chatListUsers[loggedOutUser].online = 'N'; } } else { /* Updating entire chatlist if user logs in. */ this.chatListUsers = chatListResponse.chatList; } this.loading = false; } else { alert(`Unable to load Chat list, Redirecting to Login.`); this.chatService.removeLS() .then(async (removedLs: boolean) => { await this.router.navigate(['/']); this.loading = false; }) .catch(async (error: Error) => { alert(' This App is Broken, we are working on it. try after some time.'); await this.router.navigate(['/']); console.warn(error); this.loading = false; }); } }
We know what the above code does, let’s understand how the above code works!
Explanation :
- Using
getChatList()
method we are updating the list of the user, which is defined into theSocketService
service. - For example, If a new user logs in into the system or an existing user goes offline in both the cases we will update the list of online users.
- Here I am emitting the complete list of online/offline users from the nodejs socket server to the user who justlogged into the system.
- And, for the rest of users, those who are already online will get the users information whowent offlineorjust logged in. This makes sense, right? Because sending the complete list of online users to each and every user is not good.
- Now if you see the above code there is
else if()
code block,for the below task. - If the response from the socket event contains thesingleUser propertywhich means the new socket connection is established.
- If the response contains theuserDisconnected propertythen it means the user went and offline remove the user from the chat list.
- And at the end, In the else condition we will update the list of users in the else block.
Writing Socket Server Events to Emit the updated Chat List
All the above only happen if you have Socket server and which responds to the updated chat list. Anyways, let’s go ahead and talk something constructive.
=> Here you will write socket.io.on
listener on the server side when a client requests it.
=> Once Angular asks for chat list to Socker server, the socket will query the details from the database.
=>After querying, based on the user it emits the result.
So this was the basic idea, let’s see the code to implement the same. Open the socket.js and add the below code,
socket.js:
/* * Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */socket.on(`chat-list`, async (data) => { if (data.userId == '') { this.io.emit(`chat-list-response`, { error : true, message : CONSTANTS.USER_NOT_FOUND }); }else{ try { const [UserInfoResponse, chatlistResponse] = await Promise.all([ queryHandler.getUserInfo( { userId: data.userId, socketId: false }), queryHandler.getChatList( socket.id ) ]); this.io.to(socket.id).emit(`chat-list-response`, { error : false, singleUser : false, chatList : chatlistResponse }); socket.broadcast.emit(`chat-list-response`,{ error : false, singleUser : true, chatList : UserInfoResponse }); } catch ( error ) { this.io.to(socket.id).emit(`chat-list-response`,{ error : true , chatList : [] }); } } });
Explanation:
The above code seems easy to understand. But me being me, am gonna explain it anyway.
- First thing first, we will check for the validation. If all goes well, process ahead else emit a response with an error message.
- Second, inside the try-catch block, we will execute all the things. Why ? because you never know when your code will explode.
- Just kidding,try-catch is there to handle the unknown exceptions and unhandled promise rejections. And I feel like it’s a good practice to follow.
- Then inside the try-catch block, you will first fetch the information about the user by calling
getUserInfo()
. Later you all thegetChatList()
which is defined inside theQueryHandler
class. - Why are you emitting two socket Events?As I explained in the above section, you don’t wanna send the entire chat list to the user instead send only information about the user who goes online/offline.
Let’s take a look at those method defined in theQueryHandler
class. Open the query-handler.js and add the below code,
In the below code, We are just executing a MongoDB query. Based on the socketId parameter, we are projecting the result from the database.
query-handler.js:
/* * Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */getUserInfo({userId,socketId = false}){ let queryProjection = null; if(socketId){ queryProjection = { "socketId" : true } } else { queryProjection = { "username" : true, "online" : true, '_id': false, 'id': '$_id' } } return new Promise( async (resolve, reject) => { try { const [DB,ObjectID] = await this.Mongodb.onConnect(); DB.collection('users').aggregate([{ $match: { _id : ObjectID(userId) } },{ $project : queryProjection } ]).toArray( (err, result) => { DB.close(); if( err ){ reject(err); } socketId ? resolve(result[0]['socketId']) : resolve(result); }); } catch (error) { reject(error) } }); }
Now the last method left is to fetch the chat list from the database. Honestly Speaking you are just fetching all the user from the database and displaying it to the front end based on the online/offline status.
So don’t get surprised you if you see me querying all the user from the database. in case if you want to build a really custom chat list, then I say this again, Download the above E-book.
Open the query-handler.js and add the below code. In the below code, we will fetch all the records from the database.
query-handler.js:
/* * Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */ getChatList(userId){ return new Promise( async (resolve, reject) => { try { const [DB,ObjectID] = await this.Mongodb.onConnect(); DB.collection('users').aggregate([{ $match: { 'socketId': { $ne : userId} } },{ $project:{ "username" : true, "online" : true, '_id': false, 'id': '$_id' } } ]).toArray( (err, result) => { DB.close(); if( err ){ reject(err); } resolve(result); }); } catch (error) { reject(error) } }); }
In the above code, we are executing the MongoDB query. This query will fetch the list of users from the database by using the given condition. After executing the query, it will return the result trough Promise.
Once you implement all this you should be properly working chat list as shown in below image,
Realtime Chat list
Selecting a user from the chat list to chat
Till now you have successfully implemented the chat list feature in your application. Let’s go one more step ahead and add the feature to highlight the user selected for the chatting.
=>In this section we will just highlight the selected user from the chat list.
=> Open the chat-list.componet.html and replace the code, Here we are using bootstrap classes, so you don’t need to write any CSS for that.
chat-list.componet.html:
<!-- Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io @author Shashank Tiwari --> <ul class="user-list"> <li *ngFor="let user of chatListUsers" (click)="selectedUser(user)" [class.active]="isUserSelected(user.id)" > {{ user.username}} <span [ngClass]="user.online === 'Y' ? 'online' : 'offline'"> </span> </li> </ul> <div class="alert" alert-info.class="!loading" *ngIf='chatListUsers.length === 0'> {{ loading && chatListUsers.length === 0 ? 'Loading your chat list.' : 'No User Avilable to chat.'}} </div>
Explanation:
- As you can see the method
isUserSelected()
will return true or false, that’s the only motive of that method. - It will compare the user selected userId with the user id that you are passing inside
isUserSelected()
method.
I won’t explain the entire working of this, but I have written a separate post of the same. Make sure you read, how to Highlight selected row in Angular 2, that will help you to understand this.
=> Open the chat-list.compoenet.ts and add the below property in the ChatListComponent class,
private selectedUserId: string = null;
This property will hold the value of selected user id. To complete this feature, add the below methods in thechat-list.compoenet.ts.
chat-list.compoenet.ts:
/* * Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */ isUserSelected(userId: string): boolean { if (!this.selectedUserId) { return false; } return this.selectedUserId === userId ? true : false; } selectedUser(user: User): void { this.selectedUserId = user.id; this.dataShareService.changeSelectedUser(user); }
Explanation:
isUserSelected()
method will return true or false based on a comparison ofselectedUserId
anduserId
.selectedUser()
method will update theselectedUserId
property. Also, this method will emit the latest selected user id using behavior subject.
Finishing Note
I believe this part of the series was short as compare to others. Let’s recall one more time, what we implemented here before wrapping this part. We implemented below-listed features in our application,
- We connected our Angular application to Socket Server.
- We implemented the Realtime chat list for the application.
- We wrote Socket Server Event to Emit the updated Chat List.
- Highlighted the select user from the list of users
For now, that’s it. I’ll see you in next and the last of the series where will implement real-time chat feature.
Are you taking on any custom work with this app ?
Replied you on your email.
can u explain where where exactly i need to put this code
“this.router.navigate([‘/home/’+result.userId]);”
at app.routing.ts thanks alot
Thank you 🙂
socket.request._query[‘userId’] is give undefined object