Building Chat Application using MEAN Stack (Angular 4) and Socket.io

by Didin J. on Apr 02, 2017 Building Chat Application using MEAN Stack (Angular 4) and Socket.io

Step by step tutorial of building simple chat application using MEAN stack (Angular 4) and Socket.io.

This is another tutorial of Angular 4 as part of MEAN Stack. Right now, we will build simple real chat application using MEAN Stack (Angular 4) and Socket.io. And this is our chat app will look like.

Building Chat Application using MEAN Stack (Angular 4) and Socket.io - Result

If you not sure where to start, please refer this tutorial. Don't worry, we always start tutorial from scratch, so you won't miss anything.

Prepare this tools before we start.

- Node.js
- Express.js
- Angular CLI
- Socket.io


1. Create Angular 4 Application using Angular CLI

After you have successfully install Node.js, now install or upgrade Angular CLI. The last version of angular CLI automatically add Angular 4 dependencies instead of Angular 2. Open terminal or Node command line then type this command.

sudo npm install @angular/cli -g

Next, go to your Node project folder then create new Angular 4 application by type this command.

ng new mean-chat

Open the package.json file in the root of this Angular project. You will see a difference between this project and previous Angular CLI project.

"dependencies": {
  "@angular/common": "^4.0.0",
  "@angular/compiler": "^4.0.0",
  "@angular/core": "^4.0.0",
  "@angular/forms": "^4.0.0",
  "@angular/http": "^4.0.0",
  "@angular/platform-browser": "^4.0.0",
  "@angular/platform-browser-dynamic": "^4.0.0",
  "@angular/router": "^4.0.0",
  "core-js": "^2.4.1",
  "rxjs": "^5.1.0",
  "zone.js": "^0.8.4"
},

Now, all Angular dependencies that generated from Angular CLI already Angular 4.

Go to the newly created Angular 4 project in the terminal.

cd mean-chat

Run the Angular 2 application by typing this command.

ng serve --prod

Open your browser then point to this address "http://localhost:4200" and you will see this page in your browser.

Building Chat Application using MEAN Stack (Angular 4) and Socket.io - Angular Home Page

 


2. Add Express.js to the Angular 4 Project

Stop the server by push keyboard Ctrl+C. Type this command to install Express.js and required dependencies.

npm install --save express body-parser morgan body-parser serve-favicon

Create a folder with the name "bin" and add a file with the name "www" on the root of the project.

mkdir bin
touch bin/www

Fill "www" file with this codes.

#!/usr/bin/env node

/**
 * Module dependencies.
 */

var app = require('../app');
var debug = require('debug')('mean-app:server');
var http = require('http');

/**
 * Get port from environment and store in Express.
 */

var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

/**
 * Create HTTP server.
 */

var server = http.createServer(app);

/**
 * Listen on provided port, on all network interfaces.
 */

server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

/**
 * Normalize a port into a number, string, or false.
 */

function normalizePort(val) {
  var port = parseInt(val, 10);

  if (isNaN(port)) {
    // named pipe
    return val;
  }

  if (port >= 0) {
    // port number
    return port;
  }

  return false;
}

/**
 * Event listener for HTTP server "error" event.
 */

function onError(error) {
  if (error.syscall !== 'listen') {
    throw error;
  }

  var bind = typeof port === 'string'
    ? 'Pipe ' + port
    : 'Port ' + port;

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges');
      process.exit(1);
      break;
    case 'EADDRINUSE':
      console.error(bind + ' is already in use');
      process.exit(1);
      break;
    default:
      throw error;
  }
}

/**
 * Event listener for HTTP server "listening" event.
 */

function onListening() {
  var addr = server.address();
  var bind = typeof addr === 'string'
    ? 'pipe ' + addr
    : 'port ' + addr.port;
  debug('Listening on ' + bind);
}

Now, open and edit "package.json" then replace "start" value.

"scripts": {
  "ng": "ng",
  "start": "node ./bin/www",
  "build": "ng build",
  "test": "ng test",
  "lint": "ng lint",
  "e2e": "ng e2e"
},

Next, create "app.js" in the root of the project folder.

touch app.js

Open and edit app.js then add this lines of codes.

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var bodyParser = require('body-parser');

var chat = require('./routes/chat');
var app = express();

app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({'extended':'false'}));
app.use(express.static(path.join(__dirname, 'dist')));

app.use('/chat', chat);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

Create routes for the chat.

mkdir routes
touch routes/chat.js

Open and edit "chat.js" then add this lines.

var express = require('express');
var router = express.Router();

router.get('/', function(req, res, next) {
  res.send('Express REST API');
});

module.exports = router;

Now, start Express.js server by typing this command.

nodemon

or

npm start

Open the browser then point to this address "http://localhost:3000". You will see this page.

Building Chat Application using MEAN Stack (Angular 4) and Socket.io - Express Home page

Then change the address to "http://localhost:3000/chat". It will show this page.

Building Chat Application using MEAN Stack (Angular 4) and Socket.io - Angular Home inside Express

 


3. Create REST API for Accessing Chat Data

We will access chat data using REST API. Before go further, install Mongoose.js first as an ODM/ORM of MongoDB. After stopping the server, type this command for it.

npm install --save mongoose

Add this require to app.js.

var mongoose = require('mongoose');

Add this lines after require to make a connection to MongoDB.

mongoose.Promise = global.Promise;

mongoose.connect('mongodb://localhost/mean-chat')
  .then(() =>  console.log('connection successful'))
  .catch((err) => console.error(err));

Now, we will install Socket.io and Socket.io client library. Type this command to achieve it.

npm install --save socketio socket.io-client

Next, we will create single collections for hold chat data. For that, create a model folder and file for building Mongoose Model Schema.

mkdir models
touch models/Chat.js

Open and edit "models/Chat.js" then add this lines of codes.

var mongoose = require('mongoose');

var ChatSchema = new mongoose.Schema({
  room: String,
  nickname: String,
  message: String,
  updated_at: { type: Date, default: Date.now },
});

module.exports = mongoose.model('Chat', ChatSchema);

Open and edit again "routes/chat.js" then replace all codes with this.

var express = require('express');
var router = express.Router();
var mongoose = require('mongoose');
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io')(server);
var Chat = require('../models/Chat.js');

server.listen(4000);

// socket io
io.on('connection', function (socket) {
  console.log('User connected');
  socket.on('disconnect', function() {
    console.log('User disconnected');
  });
  socket.on('save-message', function (data) {
    console.log(data);
    io.emit('new-message', { message: data });
  });
});

/* GET ALL CHATS */
router.get('/:room', function(req, res, next) {
  Chat.find({ room: req.params.room }, function (err, chats) {
    if (err) return next(err);
    res.json(chats);
  });
});

/* SAVE CHAT */
router.post('/', function(req, res, next) {
  Chat.create(req.body, function (err, post) {
    if (err) return next(err);
    res.json(post);
  });
});

module.exports = router;

In that code, we are running Socket.io to listen for 'save-message' that emitted from the client and emit 'new-message' to the clients. Now, re-run our MEAN application. Open another terminal then type this command to test REST API.

curl -i -H "Accept: application/json" localhost:3000/book

If that command return response like below then REST API is ready to go.

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 2
ETag: W/"2-11FxOYiYfpMxmANj4kGJzg"
Date: Sat, 18 Mar 2017 23:01:05 GMT
Connection: keep-alive


4. Create Angular 4 Provider or Service

For serving asynchronous access to REST API, we will create service or provider. Just type this simple command to create it.

ng g s chat

Open and edit "src/app/app.module.ts" then add this import for chat service.

import { ChatService } from './chat.service';

Also add ChatService to @NgModule providers.

providers: [ChatService],

Open and edit "src/app/chat.service.ts" then replace all codes with this.

import { Injectable } from '@angular/core';
import { Http, Headers } from '@angular/http';
import 'rxjs/add/operator/map';

@Injectable()
export class ChatService {

  constructor(private http: Http) { }

  getChatByRoom(room) {
    return new Promise((resolve, reject) => {
      this.http.get('/chat/' + room)
        .map(res => res.json())
        .subscribe(res => {
          resolve(res);
        }, (err) => {
          reject(err);
        });
    });
  }

  saveChat(data) {
    return new Promise((resolve, reject) => {
        this.http.post('/chat', data)
          .map(res => res.json())
          .subscribe(res => {
            resolve(res);
          }, (err) => {
            reject(err);
          });
    });
  }

}

That codes using Promise response instead of Observable.

 


5. Create Angular 4 Component for Chat

Because our application using single page. We only need to create one Angular 4 component. Type this command to achieve that.

ng g c chat

Open and edit "src/app/chat/chat.component.ts" then add this lines of codes.

import { Component, OnInit, AfterViewChecked, ElementRef, ViewChild } from '@angular/core';
import { ChatService } from '../chat.service';
import * as io from "socket.io-client";

@Component({
  selector: 'app-chat',
  templateUrl: './chat.component.html',
  styleUrls: ['./chat.component.css']
})
export class ChatComponent implements OnInit, AfterViewChecked {

  @ViewChild('scrollMe') private myScrollContainer: ElementRef;

  chats: any;
  joinned: boolean = false;
  newUser = { nickname: '', room: '' };
  msgData = { room: '', nickname: '', message: '' };
  socket = io('http://localhost:4000');

  constructor(private chatService: ChatService) {}

  ngOnInit() {
    var user = JSON.parse(localStorage.getItem("user"));
    if(user!==null) {
      this.getChatByRoom(user.room);
      this.msgData = { room: user.room, nickname: user.nickname, message: '' }
      this.joinned = true;
      this.scrollToBottom();
    }
    this.socket.on('new-message', function (data) {
      if(data.message.room === JSON.parse(localStorage.getItem("user")).room) {
        this.chats.push(data.message);
        this.msgData = { room: user.room, nickname: user.nickname, message: '' }
        this.scrollToBottom();
      }
    }.bind(this));
  }

  ngAfterViewChecked() {
    this.scrollToBottom();
  }

  scrollToBottom(): void {
    try {
      this.myScrollContainer.nativeElement.scrollTop = this.myScrollContainer.nativeElement.scrollHeight;
    } catch(err) { }
  }

  getChatByRoom(room) {
    this.chatService.getChatByRoom(room).then((res) => {
      this.chats = res;
    }, (err) => {
      console.log(err);
    });
  }

  joinRoom() {
    var date = new Date();
    localStorage.setItem("user", JSON.stringify(this.newUser));
    this.getChatByRoom(this.newUser.room);
    this.msgData = { room: this.newUser.room, nickname: this.newUser.nickname, message: '' };
    this.joinned = true;
    this.socket.emit('save-message', { room: this.newUser.room, nickname: this.newUser.nickname, message: 'Join this room', updated_at: date });
  }

  sendMessage() {
    this.chatService.saveChat(this.msgData).then((result) => {
      this.socket.emit('save-message', result);
    }, (err) => {
      console.log(err);
    });
  }

  logout() {
    var date = new Date();
    var user = JSON.parse(localStorage.getItem("user"));
    this.socket.emit('save-message', { room: user.room, nickname: user.nickname, message: 'Left this room', updated_at: date });
    localStorage.removeItem("user");
    this.joinned = false;
  }

}

That codes describe all functionality of this simple chat application. Now, we will build a view for the chat room. Before edit chat component, open and edit "src/index.html" then replace all codes so it will look like this.

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>MeanChat</title>
  <base href="/">

  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
  <!-- Latest compiled and minified CSS -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
  <!-- Optional theme -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
</head>
<body>
  <app-root>Loading...</app-root>
  <!-- jQuery library -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.0/jquery.min.js"></script>
  <!-- Latest compiled and minified JavaScript -->
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>

We are about add bootstrap for styling the chat room. Next, open and edit "src/app/chat/chat.component.html" then replace all codes with this codes.

<div class="container">
    <div class="row">
        <div class="col-md-5">
            <div class="panel panel-primary" *ngIf="joinned; else joinroom">
                <div class="panel-heading">
                    <span class="glyphicon glyphicon-comment"></span> {{ msgData.room }}
                    <div class="btn-group pull-right">
                        <button type="button" class="btn btn-default btn-xs" (click)="logout()">
                            Logout
                        </button>
                    </div>
                </div>
                <div #scrollMe class="panel-body">
                    <ul class="chat">
                        <li *ngFor="let c of chats">
                          <div class="left clearfix" *ngIf="c.nickname===msgData.nickname; else rightchat">
                            <span class="chat-img pull-left">
                              <img src="http://placehold.it/50/55C1E7/fff&text=ME" alt="User Avatar" class="img-circle" />
                            </span>
                            <div class="chat-body clearfix">
                                <div class="header">
                                    <strong class="primary-font">{{ c.nickname }}</strong> <small class="pull-right text-muted">
                                        <span class="glyphicon glyphicon-time"></span>{{ c.updated_at | date: 'medium' }}</small>
                                </div>
                                <p>{{ c.message }}</p>
                            </div>
                          </div>
                          <ng-template #rightchat>
                            <div class="right clearfix">
                              <span class="chat-img pull-right">
                                <img src="http://placehold.it/50/FA6F57/fff&text=U" alt="User Avatar" class="img-circle" />
                              </span>
                              <div class="chat-body clearfix">
                                  <div class="header">
                                      <small class=" text-muted"><span class="glyphicon glyphicon-time"></span>{{ c.updated_at | date: 'medium' }}</small>
                                      <strong class="pull-right primary-font">{{ c.nickname }}</strong>
                                  </div>
                                  <p>{{ c.message }}</p>
                              </div>
                            </div>
                          </ng-template>
                        </li>
                    </ul>
                </div>
                <div class="panel-footer">
                  <form (ngSubmit)="sendMessage()" #msgForm="ngForm">
                    <div class="input-group">
                        <input type="hidden" [(ngModel)]="msgData.room" name="room" />
                        <input type="hidden" [(ngModel)]="msgData.nickname" name="nickname" />
                        <input id="btn-input" type="text" [(ngModel)]="msgData.message" name="message" class="form-control input-sm" placeholder="Type your message here..." required="" />
                        <span class="input-group-btn">
                            <button class="btn btn-warning btn-sm" id="btn-chat" [disabled]="!msgForm.form.valid">
                                Send</button>
                        </span>
                    </div>
                  </form>
                </div>
            </div>
            <ng-template #joinroom>
              <div class="panel panel-primary">
                <div class="panel-body">
                  <h1>Select Chat Room</h1>
                  <form (ngSubmit)="joinRoom()" #joinForm="ngForm">
                    <div class="form-group">
                      <input type="text" class="form-control" [(ngModel)]="newUser.nickname" name="nickname" placeholder="Nickname" required="" />
                    </div>
                    <div class="form-group">
                      <select class="form-control" [(ngModel)]="newUser.room" name="room" required="">
                        <option>Select Room</option>
                        <option value="Javascript">Javascript</option>
                        <option value="Java">Java</option>
                        <option value="Python">Python</option>
                      </select>
                    </div>
                    <div class="form-group">
                      <button type="submit" class="btn btn-success" [disabled]="!joinForm.form.valid">Join</button>
                    </div>
                  </form>
                </div>
              </div>
            </ng-template>
        </div>
    </div>
</div>

Give that view a litle style by edit "src/app/chat/chat.component.css" then replace all codes with this.

.chat
{
    list-style: none;
    margin: 0;
    padding: 0;
}

.chat li
{
    margin-bottom: 10px;
    padding-bottom: 5px;
    border-bottom: 1px dotted #B3A9A9;
}

.chat li.left .chat-body
{
    margin-left: 60px;
}

.chat li.right .chat-body
{
    margin-right: 60px;
}


.chat li .chat-body p
{
    margin: 0;
    color: #777777;
}

.panel .slidedown .glyphicon, .chat .glyphicon
{
    margin-right: 5px;
}

.panel-body
{
    overflow-y: scroll;
    height: 250px;
}

::-webkit-scrollbar-track
{
    -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
    background-color: #F5F5F5;
}

::-webkit-scrollbar
{
    width: 12px;
    background-color: #F5F5F5;
}

::-webkit-scrollbar-thumb
{
    -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
    background-color: #555;
}

Finally, build and run this application by type this commands.

ng build --prod
npm start

Now, you can try chatting in multiple different browsers in your PC/Laptop or different PC/Laptop by pointing to this address "localhost:3000" or "your-ip-address:3000".

 

Warning: This tutorial is experiments of Angular 4 with MEAN Stack. We suggest don't try this tutorial in production.

That it's for now, please give us suggestion or critics to improve this tutorial. If you feel there's is something wrong with this tutorial that you follow, don't worry we have put the complete source code on our Github.

Thanks.
 

Loading…