This is the second part of the Building Real time private chatting app using Angular. In this part, you will implement below-listed features in your application,
- Registration.
- Checking uniqueness of the username before registration.
- Login.
- Checking the Session of a user (when the user navigates to the home page).
In the First part of the Series, we did the setup of Angular and Nodejs application. Since I have created boilerplates for Both Angular and Nodejs, therefore you don’t have to do the monotonous work for your self.
I assume you have cloned or downloaded the Angular boilerplate and Nodejs boilerplate. I highly recommend you to clone/download, If you haven’t cloned/downloaded these boilerplates already.
If you use Boilerplates, your application will be less prone to mistakes. Since we are using them to make HTTP calls and listen to the real-time event of the socket event, hence I won’t share and explain the source code of services.
Okay, Now install and run both the boilerplates that you have for Angular application and Nodejs server. II hope till this point, you are with me. And somehow if you are having any trouble let me know.
Implementing Registration Feature:
Let’s start by implementing Registration feature. I don’t think I have much to talk about the importance of registration feature. First, we will write the client side code. Later we will write node APIs and all code related to it.
=> So the first step is writing MarkUp, so open the auth.component.html and write down the below code.
auth.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> <div class="container"> <div class="row login-screen"> <div class="offset-md-3 col-md-5 justifier"> <ngb-tabset [justify]='setTabPosition'> <ngb-tab title="Login"> <ng-template ngbTabContent> <!-- Markup for Login Goes Here --> </ng-template> </ngb-tab> <ngb-tab title="Registration"> <ng-template ngbTabContent> <div class="justifier"> <form (ngSubmit)="register()" [formGroup]="registrationForm" novalidate> <div class="form-group"> <label>Name</label> <input type="text" name="username" class="form-control" placeholder="Enter your Username" formControlName="username" autocomplete="off"> <div class="alert alert-danger feedback" *ngIf="!registrationForm.controls['username'].valid && (registrationForm.controls['username'].dirty || registrationForm.controls['username'].touched)"> <span *ngIf="registrationForm.controls['username'].errors.required"> Username is required </span> </div> <div class="alert alert-danger feedback" *ngIf="isuserNameAvailable && (registrationForm.controls['username'].dirty || registrationForm.controls['username'].touched)"> username <strong>{{registrationForm.controls['username'].value}}</strong> is already taken. </div> </div> <div class="form-group"> <label>Password</label> <input type="password" name="password" class="form-control" placeholder="Enter your Password" formControlName="password"> <div class="feedback" *ngIf="!registrationForm.controls['password'].valid && (registrationForm.controls['password'].dirty || registrationForm.controls['password'].touched)"> <span *ngIf="registrationForm.controls['password'].errors.required"> Password is required </span> <span *ngIf="registrationForm.controls['password'].errors.minlength"> Password must be 8 characters long, we need another {{registrationForm.controls['password'].errors.minlength.requiredLength - registrationForm.controls['password'].errors.minlength.actualLength}} characters </span> </div> </div> <div class="form-group"> <button class="btn btn-primary" [disabled]="!registrationForm.valid">Registration</button> </div> </form> </div> </ng-template> </ngb-tab> </ngb-tabset> </div> </div> </div>
Explanation:
- The first thing to notice is here is we are using ng-bootstrap.
- If you are not familiar with it, I would recommend you to check it and use it.
- After that, we have a typical Angular Form and some message that would be visible when validation fails.
- Nothing difficult to understand here also the picture will be more clear once you will write the code inside the component class.
To make your markup work, you will have to write some code inside your component class. Open the auth.component.ts and write down the below code.
=> In the below code, we will create an instance of FormGroup. You will use this FormGroup instance in your registration form.
=> The application will redirect the user to the Home Page as soon as the user finishes the registration process.
auth.component.ts:
/* * Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */ import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { FormGroup } from '@angular/forms'; /* Importing services starts*/import { ChatService } from './../../services/chat.service'; import { FormService } from './../../services/form.service'; /* Importing services ends*/ /* Importing inrerface starts*/import { Auth } from './../../interfaces/auth'; /* Importing inrerface starts*/ @Component({ selector: 'app-auth', templateUrl: './auth.component.html', styleUrls: ['./auth.component.css'] }) export class AuthComponent implements OnInit { public setTabPosition = 'center'; private registrationForm: FormGroup; constructor( private router: Router, private chatService: ChatService, private formService: FormService ) { this.registrationForm = this.formService.createRegistrationForm(); } ngOnInit() { } register(): void { if (this.registrationForm.valid) { this.overlayDisplay = false; this.chatService.register(this.registrationForm.value).subscribe( (response: Auth) => { localStorage.setItem('userid', response.userId); this.router.navigate(['/home']); }, (error) => { this.overlayDisplay = true; /* Uncomment it, Incase if you like to reset the Login Form. */ // this.registrationForm.reset(); alert('Something bad happened; please try again later.'); } ); } } }
Explanation:
- Starting with imports, First, we will import services and Interfaces. In the first part of this series, we had discussed the scope of each file, So you must know why we are using
ChatService
andFormService
. - The
setTabPosition
is used for aligning the tabs into the center. - Inside the constructor function, you will call
createRegistrationForm()
, and assign the value of it to theregistrationForm
property of the component class. - Then in registration method, you will call a
register()
method ofChatService
class by passing the values of theregistrationForm
. - Once you will get the response from the server, You have to do two things based on the response received from the server.
- First, After a successful registration you will store the
userId
into Local Storage and then you redirect the user to the Home page. - Second, You just alert an Error message if anything goes wrong.
Forgive me for showing ugly messages. For the sake of simplicity, I have used the usual alerts, instead of fancy POPUPs.
To complete the registration process you will have to write code in Nodejs as well. Basically, writing Nodejs code will piece of a cake for you, because you don’t have much to write. Let’s get to it, Open the routes.js and add the below the route.
routes.js:
this.app.post('/register', routeHandler.registerRouteHandler);
After that, you have to write few lines of code inside theregisterRouteHandler()
, just to handle the request/response and call the method which just inserts the data into Database.
Add the below code, inside the route.handler.jsfile. TheregisterRouteHandler()
will take care of the /register
route.
route.handler.js:
/* * Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */async registerRouteHandler(request, response){ const data = { username : (request.body.username).toLowerCase(), password : request.body.password }; if(data.username === '') { response.status(CONSTANTS.SERVER_ERROR_HTTP_CODE).json({ error : true, message : CONSTANTS.USERNAME_NOT_FOUND }); }else if(data.password === ''){ response.status(CONSTANTS.SERVER_ERROR_HTTP_CODE).json({ error : true, message : CONSTANTS.PASSWORD_NOT_FOUND }); }else{ try { data.online = 'Y' ; data.socketId = '' ; data.password = passwordHash.createHash(data.password); const result = await helper.registerUser(data); if (result === null || result === undefined) { response.status(CONSTANTS.SERVER_OK_HTTP_CODE).json({ error : false, message : CONSTANTS.USER_REGISTRATION_FAILED }); } else { response.status(CONSTANTS.SERVER_OK_HTTP_CODE).json({ error : false, userId : result.insertedId, message : CONSTANTS.USER_REGISTRATION_OK }); } } catch ( error ) { response.status(CONSTANTS.SERVER_NOT_FOUND_HTTP_CODE).json({ error : true, message : CONSTANTS.SERVER_ERROR_MESSAGE }); } } }
Explanation:
- In the above code, you will first check the validation. If anything wrong with that, we will send the proper error code with an Error message.
- Then we will generate the Password Hash for user entered password.
- And the end if everything is valid or in more precise words, if the request is valid than you will Register a user by calling
registerUser()
. - The
registerUser()
is defined inside the query-handler.js.
Open thequery-handler.jsfile and write down the below code. As you have figured it by now, You will just have to insert the records in MongoDB in the below code.
query-handler.js:
/* * Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io * @author Shashank Tiwari * * Name of the Method : registerUser * Description : register the User * Parameter : * 1) data query object for MongoDB * Return : Promise */registerUser(data){ return new Promise( async (resolve, reject) => { try { const [DB,ObjectID] = await this.Mongodb.onConnect(); DB.collection('users').insertOne(data, (err, result) =>{ DB.close(); if( err ){ reject(err); } resolve(result); }); } catch (error) { reject(error) } }); }
Explanation:
- In the above code, First thing you will do is return promise. By returning promise you can save yourself from callback hell, and later you can apply Async/Await on this function.
- After that, you will connect the MongoDB database and insert the record into the database.
- And that’s it. You have created a new user.
Checking for a unique username
It is very important that before registration, you check the uniqueness of username. It’s obvious that you can’t have two users with the same username.
First thing first, let’s add the Markup in authentication.component.html. Here you don’t have much to write, just an indicator that shows the particular username is taken.
In the below Markup, we have used the *ngIf
directive to hide and show the Message by using the isuserNameAvailable
property of AuthenticationComponent class.
authentication.component.html:
<!-- Real time private chatting app using Angular 2, Nodejs, MongoDB, and Socket.io @author Shashank Tiwari --> <div class="alert alert-danger feedback" *ngIf="isuserNameAvailable && (registrationForm.controls['username'].dirty || registrationForm.controls['username'].touched)"> username <strong>{{registrationForm.controls['username'].value}}</strong> is already taken. </div>
The above markup will hide/show only if you write something in your component class file.
First of all, add the below property in your AuthenticationComponent class,
public isuserNameAvailable = false;
After that add the below in AuthenticationComponent class, The below code will check the username uniqueness by Making an HTTP request to your Nodejs server. Open the authentication.component.ts and add the below code.
authentication.component.ts:
/* * Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */getUsernameSuggestion(): void { this.registrationForm.controls['username'].valueChanges .pipe( map((term) => { this.isuserNameAvailable = false; return term; }) ) .pipe( debounceTime(800), distinctUntilChanged() ) .subscribe((term: string) => { if (term !== '') { this.overlayDisplay = true; this.chatService.usernameAvailable(term).subscribe((response: UsernameAvailable) => { this.overlayDisplay = false; if (response.error) { this.isuserNameAvailable = true; } else { this.isuserNameAvailable = false; } }); } }); }
Explanation:
Add this method inAuthComponent class, now let’s break this method from the top to bottom and understand what it does.
- Using
valueChanges
you can subscribe to any value of the Form. Whenever a value changes in the field of a form you will get to know the new value. debounceTime
will add an extra delay of 800ms. Am sure you don’t want to make HTTP calls as soon as the user starts typing Right?distinctUntilChanged
will make your subscriber listen only for distinct values.- And at the end, if everything goes well and good, then you will call
ChatService
to check the uniqueness of the user. - Based on the response, the Angular will hide/show the username uniqueness indicator.
This is not complete yet, you still have to code for your Nodejs part. Writing Nodejs routes and its helper will be the same as it was in the last section, In fact, it’s going to be identical for Nodejs routes. So first as usual open the routes.js and add the below the route.
routes.js:
this.app.post('/usernameAvailable', routeHandler.userNameCheckHandler);
Now our express route is added, let’s write a method to handle that request in the route-handler.js file. Open that file and add the below code,
route-handler.js:
/* * Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */async userNameCheckHandler(request, response){ const username = request.body.username; if (username === "") { response.status(CONSTANTS.SERVER_ERROR_HTTP_CODE).json({ error : true, message : CONSTANTS.USERNAME_NOT_FOUND }); } else { try { const count = await helper.userNameCheck( { username : username.toLowerCase() }); if (count > 0) { response.status(200).json({ error : true, message : CONSTANTS.USERNAME_AVAILABLE_FAILED }); } else { response.status(200).json({ error : false, message : CONSTANTS.USERNAME_AVAILABLE_OK }); } } catch ( error ){ response.status(CONSTANTS.SERVER_ERROR_HTTP_CODE).json({ error : true, message : CONSTANTS.SERVER_ERROR_MESSAGE }); } } }
If you notice we have calleduserNameCheck()
method to check the user count of same usernames. This method will basically check the counts of the given username and returns that count to the callee.
Open the query-handler.js and add the below code, in the below code you just a run a MongoDB query and return the response to caller method.
query-handler.js:
/* * Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io * @author Shashank Tiwari * * Name of the Method : userNameCheck * Description : To check if the username is available or not. * Parameter : * 1) data query object for MongDB * Return : Promise */userNameCheck(data){ return new Promise( async (resolve, reject) => { try { const [DB,ObjectID] = await this.Mongodb.onConnect(); DB.collection('users').find(data).count( (error, result) => { DB.close(); if( error ){ reject(error); } resolve(result); }); } catch (error) { reject(error) } }); }
Now, this completes your Checking for a unique username features. Congrats, you have added one more feature to your application.
Implementing Login Feature:
Implementation of Login feature is going to pretty easy and almost identical to above-implemented features. First, we will start with Markup, then component and in the end, we will write some NodeJs just like we did in the last two sections.
The below markup will render the Login form, which will have two fields username and password. Open the authentication.component.html and add the below inside the Login Tab.
authentication.component.html:
<!-- Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io @author Shashank Tiwari --> <div class="justifier"> <form (ngSubmit)="login()" [formGroup]="loginForm" novalidate> <div class="form-group"> <label>Username</label> <input type="text" name="username" class="form-control" placeholder="Enter your Username" formControlName='username' autocomplete="off"> <div class="alert alert-danger feedback" *ngIf="!loginForm.controls['username'].valid && (loginForm.controls['username'].dirty || loginForm.controls['username'].touched)"> <span *ngIf="loginForm.controls['username'].errors.required"> Username is required </span> </div> </div> <div class="form-group"> <label>Password</label> <input type="password" name="password" class="form-control" placeholder="Enter your Password" formControlName='password'> <div class="alert alert-danger feedback" *ngIf="loginForm.controls['password'].invalid && (loginForm.controls['password'].dirty || loginForm.controls['password'].touched)"> <span *ngIf="loginForm.controls['password'].invalid"> Password is invalid </span> </div> </div> <div class="alert alert-danger feedback" *ngIf="loginError"> Inavlid Username Password combination. </div> <div class="form-group"> <button type='submit' class="btn btn-primary" [disabled]="!loginForm.valid">Login</button> </div> </form> </div>
To make our Login Form fully functional, you will have to write some TypeScript in your Component class. First thing first, you need to initialize the Login Form by calling FormService
. Form service will basically give you an instance of the Login form.
Add the below property to the AuthenticationComponent class.
authentication.component.ts:
private loginForm: FormGroup;
As of now, loginForm
is empty having a type as FromGroup
. To give the instance of form field you have to call FormService
.
Add the below line to the constructor function.
authentication.component.ts:
this.loginForm = this.formService.createLoginForm();
Our Angular Form is ready now, Only thing is left to handle a Form submission. We will write a login()
method in AuthenticationComponent
class to do that.
Open the authentication.component.ts and write down the below code.
authentication.component.ts:
/* * Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */login(): void { if (this.loginForm.valid) { this.overlayDisplay = true; this.chatService.login(this.loginForm.value).subscribe( (response: Auth) => { this.overlayDisplay = false; localStorage.setItem('userid', response.userId); this.router.navigate(['/pages/home']); }, (error) => { /* Uncomment it, Incase if you like to reset the Login Form. */ // this.loginForm.reset(); this.overlayDisplay = false; this.loginError = true; } ); } }
Let’s write the Nodejs part, Here we will do three things First we will add the route. Second, we will add a method to handle the request/response of the route and at the end method to perform the Login.
So let’s just add the route, open the routes.js. and add the below route.
routes.js:
this.app.post('/login', routeHandler.loginRouteHandler);
The/login
route is added to its respective file, now let’s write the method to handle the response to the/login
route. Open the route-handler.js and add the below code,
route-handler.js:
/* * Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */async loginRouteHandler(request, response){ const data = { username : (request.body.username).toLowerCase(), password : request.body.password }; if(data.username === '' || data.username === null) { response.status(CONSTANTS.SERVER_ERROR_HTTP_CODE).json({ error : true, message : CONSTANTS.USERNAME_NOT_FOUND }); }else if(data.password === '' || data.password === null){ response.status(CONSTANTS.SERVER_ERROR_HTTP_CODE).json({ error : true, message : CONSTANTS.PASSWORD_NOT_FOUND }); }else{ try { const result = await queryHandler.getUserByUsername(data.username); if(result === null || result === undefined) { response.status(CONSTANTS.SERVER_NOT_FOUND_HTTP_CODE).json({ error : true, message : CONSTANTS.USER_LOGIN_FAILED }); } else { if( passwordHash.compareHash(data.password, result.password) ){ await queryHandler.makeUserOnline(result._id); response.status(CONSTANTS.SERVER_OK_HTTP_CODE).json({ error : false, userId : result._id, message : CONSTANTS.USER_LOGIN_OK }); } else { response.status(CONSTANTS.SERVER_NOT_FOUND_HTTP_CODE).json({ error : true, message : CONSTANTS.USER_LOGIN_FAILED }); } } } catch (error) { response.status(CONSTANTS.SERVER_NOT_FOUND_HTTP_CODE).json({ error : true, message : CONSTANTS.USER_LOGIN_FAILED }); } } }
Explanation:
- There is a lot of things going on here, First, we will do the validation if everything goes fine, then we will actually fetch the result from the database and verify it with the data sent from the Angular application.
- After validation and stuff, we will fetch the record of the user by using his/her username.
- If you get a result from the database using the username, then go ahead compare the password of a user by using
compareHash()
method. - After comparing the password, based on the result you will make the status of user online.
Checking User’s Session
It is very critical to check session of the user before allowing the users to chat and read messages. To check the session of the user, we will make an HTTP call.
Whenever a user visits the home page of the application we will check the session. And by the way, we won’t make an HTTP call to check the user’s session.
So basically we will setup Angular Gaurd for /home
route to do that work for us.
If you are using the boilerplate, you will find a file named asauth-guard.service.ts in the folder /services
. I assume that you have prior knowledge Angular Route guard.
So all you have to do is uncomment thecanActivate
property in pages-routing.module.ts file and you are done.
pages-routing.module.ts :
/* * Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { AuthGuardService as AuthGuard } from './../services/auth-guard/auth-guard.service'; import { PagesComponent } from './pages.component'; const routes: Routes = [{ path: '', component: PagesComponent, children: [{ path: '', redirectTo: 'authentication', pathMatch: 'full' }, { path: 'authentication', loadChildren: './authentication/authentication.module#AuthenticationModule' }, { path: 'home', loadChildren: './home/home.module#HomeModule', canActivate: [AuthGuard] }, { path: '**', redirectTo: 'home' }] }]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) export class PagesRoutingModule { }
Now let’s add the route for session check service in the routes.js file. Here we will check the online status of the user, and based on the online status we will determine the session of that user. So add the below route in the routes.js file.
routes.js:
this.app.post('/userSessionCheck', routeHandler.userSessionCheckRouteHandler);
As always, we have to write method inside theroute-handler.js file to handle the response for the /userSessionCheck
route. As you can see, in this case, we are callinguserSessionCheckRouteHandler()
to handle the response for the /userSessionCheck
.
Open the route-handler.js and add the below code,
route-handler.js :
/* * Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */async userSessionCheckRouteHandler(request, response){ let userId = request.body.userId; if (userId === '') { response.status(CONSTANTS.SERVER_ERROR_HTTP_CODE).json({ error : true, message : CONSTANTS.USERID_NOT_FOUND }); }else{ try { const result = await queryHandler.userSessionCheck({ userId : userId }); response.status(CONSTANTS.SERVER_OK_HTTP_CODE).json({ error : false, username : result.username, message : CONSTANTS.USER_LOGIN_OK }); } catch ( error ) { response.status(CONSTANTS.SERVER_NOT_ALLOWED_HTTP_CODE).json({ error : true, message : CONSTANTS.USER_NOT_LOGGED_IN }); } } }
Explanation:
- In the above code, First, we will check for the userId validation. If the validation fails, we will send the user not found a response.
- If validation passes, then we will check the online status of the user in Database by using userId.
- And depending on the online status we will send the response to Angular Application.
Now to check the online status in Database you will have to writeuserSessionCheck()
method in QueryHandler
class. This method will basically check the online status by executing a MongoDB Query.
Open the query-handler.js file and add the below method,
query-handler.js:
/* * Real time private chatting app using Angular 2, Nodejs, mongodb and Socket.io * @author Shashank Tiwari * * Name of the Method : userSessionCheck * Description : to check if user is online or not. * Parameter : * 1) data query object for MongDB * Return : Promise */userSessionCheck(data){ return new Promise( async (resolve, reject) => { try { const [DB,ObjectID] = await this.Mongodb.onConnect(); DB.collection('users').findOne( { _id : ObjectID(data.userId) , online : 'Y'}, (err, result) => { DB.close(); if( err ){ reject(err); } resolve(result); }); } catch (error) { reject(error) } }); }
And I assume above code needs no explanation, since you are just executing a MongoDB query and returning a Promise.
That’s it.
Conclusion
Now let’s conclude this part by recapping what we did in this part.
- We downloaded theAngular boilerplateandNodejs boilerplateand using these created our application.
- We implemented a Registration feature.
- We implemented a feature to check uniqueness of the username before registration.
- We added Login functionality in our application.
- At the end we added a feature to check the session of a User.
In next part of the series, we will connect our Angular application to the socket and implement the Realtime chat list for the application.
So stay tuned it’s going to more interesting.
I have implement to this in a global chat application but in angular 2 on routes creates observer multiple message … like. i change 2 time different page in application then it’s
receive message 2 times.. in chat box .. can you help for multiple message..
ng ‘ng’ is not recognized as an internal or external command,
operable program or batch file.
Hello sir,
Looks like it’s related to Angular CLI. Make sure you have installed Angular-CLI properly.
Please refer : https://www.npmjs.com/package/@angular/cli/tutorial
it is a very nice app and thank you for this very useful article. My question is how can we check out mongo database collection for this project . since i’m very new to mongodb so need your help to determine,
thanks
Thanks for the good words. You have two options here. First, you can use the MongoDB CLI and second use RoboMongo I personally use RoboMongo. You can install RoboMongo from here, first, download the RoboMongo 3T from here https://robomongo.org/download, follow the installation instructions.