Building an Ionic Chat with Socket.io Last update: 2017-08-22

Building an Ionic Chat with Socket.io

There are many ways to build a chat application with Ionic. You could use Firebase as a realtime database, or you can use your own Node server with some Socket.io, and that’s what we gonna do today to build a realtime Ionic Chat!

In this tutorial we will craft a super simple Node.js server and implement Socket.io on the server-side to open realtime connections to the server so we can chat with other participants of a chatroom.

This is a super lightweight example as we don’t store any of the messages on the server - if you are nor in the chat room you will never get what happened. The result will look like in the image below.

socket-chat

Building our Node Chat Server

This tutorial starts with the actual backend for our app. It’s good to have some Node skills so you could potentially build your own little backend from time to time. First of all we create a new folder and inside an NPM package file, where we also install Express as a minimal framework for our backend:

mkdir SocketServer && cd SocketServer
npm init
npm install --save express socket.io

After creating and installing you should have a package.json inside that file. Make sure inside that file the main file is set to index.js so the scripts knows which file to start!

{
  "name": "socket-server",
  "version": "1.0.0",
  "main": "index.js",
  "dependencies": {
    "express": "^4.15.3",
    "socket.io": "^2.0.3"
  }
}

Finally we got the actual index.js which contains the logic of our server. As said before, we stay really low here and just implement some basic socket functions!

All of our functions are wrapped inside the io.on('connection') block, so these will only happen once a client connects to the server.

We set a function for the events disconnect, set-nickname and add-message which means whenever our app sends out these events the server does something.

If we send a new message, we emit that message to everyone connected as a new object with text, the name of the sending user and a date. Also, we set the name of the socket connection if a users send his nickname.

Finally if a user disconnects, we inform everyone that somebody just left the room. Put all of that code into your index.js:

let app = require('express')();
let http = require('http').Server(app);
let io = require('socket.io')(http);

io.on('connection', (socket) => {

  socket.on('disconnect', function(){
    io.emit('users-changed', {user: socket.nickname, event: 'left'});
  });

  socket.on('set-nickname', (nickname) => {
    socket.nickname = nickname;
    io.emit('users-changed', {user: nickname, event: 'joined'});
  });

  socket.on('add-message', (message) => {
    io.emit('message', {text: message.text, from: socket.nickname, created: new Date()});
  });
});

var port = process.env.PORT || 3001;

http.listen(port, function(){
   console.log('listening in http://localhost:' + port);
});

Your node backend with Socket.io is now ready! You can start it by running the command below and you should be able to reach it at http://localhost:3001

node index.js

Starting the Ionic Chat App

Inside our Ionic chat app we need 2 screens: On the first screen we will pick a name and join the chat, on the second screen is the actual chatroom with messages.

First of all we create a blank new Ionic app and install the ng-socket-io package to easily connect to our Socket backend, so go ahead and run:

ionic start devdacticSocket blank
cd devdacticSocket
npm install ng-socket-io --save
ionic g page chatRoom

Now make sure to add the package to our src/app/app.module.ts and pass in your backend URL as a parameter:

import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';

import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { SocketIoModule, SocketIoConfig } from 'ng-socket-io';
const config: SocketIoConfig = { url: 'http://localhost:3001', options: {} };

@NgModule({
  declarations: [
    MyApp,
    HomePage
  ],
  imports: [
    BrowserModule,
    IonicModule.forRoot(MyApp),
    SocketIoModule.forRoot(config)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler}
  ]
})
export class AppModule {}

Your app is now configured to use the backend, so make sure to run the backend when launching your app!

Joining a Chatroom

First of all a user needs to pick a name to join a chatroom. This is just an example so we can actually show who wrote which message, so our view consists of the input field and a button to join the chat. Open your src/pages/home/home.html and change it to:

<ion-header>
  <ion-navbar>
    <ion-title>
      Join Chat
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  <ion-item>
    <ion-label stacked>Set Nickname</ion-label>
    <ion-input type="text" [(ngModel)]="nickname" placeholder="Nickname"></ion-input>
  </ion-item>
  <button ion-button full (click)="joinChat()" [disabled]="nickname === ''">Join Chat as {{ nickname }}</button>
</ion-content>

Inside the class for this view we have the first interaction with Socket. Once we click to join a chat, we need to call connect() so the server recognises a new connection. Then we emit the first message to the server which is to set our nickname.

Once both of this happened we push the next page which is our chatroom, go ahead and change your src/pages/home/home.ts to:

import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { Socket } from 'ng-socket-io';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {
  nickname = '';

  constructor(public navCtrl: NavController, private socket: Socket) { }

  joinChat() {
    this.socket.connect();
    this.socket.emit('set-nickname', this.nickname);
    this.navCtrl.push('ChatRoomPage', { nickname: this.nickname });
  }
}

If you put in some logs you should now already receive a connection and the event on the server-side, but we haven’t added the actual chat functionality so let’s do this.

Building the Chat functionality

To receive new chat messages inside the room we have a function getMessages() which returns an observable. Also, this message listens to Socket events of the type ’message’ and always calls next() on the observable to pass the new value through.

Whenever we get such a message we simply push the new message to an array of messages. Remember, we are not loading historic data, we will only get messages that come after we are connected!

Sending a new message is almost the same like setting a nickname before, we simply emit our event to the server with the right type.

Finally, we also subscribe to the events of users joining and leaving the room and display a little toast whenever someone comes in or leaves the room. It’s the same logic again, with the socket.on() we can listen to all the events broadcasted from our server!

Go ahead and change your app/pages/chat-room/chat-room.ts to:

import { Component } from '@angular/core';
import { NavController, IonicPage, NavParams, ToastController } from 'ionic-angular';
import { Socket } from 'ng-socket-io';
import { Observable } from 'rxjs/Observable';

@IonicPage()
@Component({
  selector: 'page-chat-room',
  templateUrl: 'chat-room.html',
})
export class ChatRoomPage {
  messages = [];
  nickname = '';
  message = '';

  constructor(private navCtrl: NavController, private navParams: NavParams, private socket: Socket, private toastCtrl: ToastController) {
    this.nickname = this.navParams.get('nickname');

    this.getMessages().subscribe(message => {
      this.messages.push(message);
    });

    this.getUsers().subscribe(data => {
      let user = data['user'];
      if (data['event'] === 'left') {
        this.showToast('User left: ' + user);
      } else {
        this.showToast('User joined: ' + user);
      }
    });
  }

  sendMessage() {
    this.socket.emit('add-message', { text: this.message });
    this.message = '';
  }

  getMessages() {
    let observable = new Observable(observer => {
      this.socket.on('message', (data) => {
        observer.next(data);
      });
    })
    return observable;
  }

  getUsers() {
    let observable = new Observable(observer => {
      this.socket.on('users-changed', (data) => {
        observer.next(data);
      });
    });
    return observable;
  }

  ionViewWillLeave() {
    this.socket.disconnect();
  }

  showToast(msg) {
    let toast = this.toastCtrl.create({
      message: msg,
      duration: 2000
    });
    toast.present();
  }
}

The last missing part is now the view for the chatroom. We have to iterate over all of our messages and distinguish if the message was from us or another user. Therefore, we create 2 different ion-col blocks as we want our messages to have some offset to a side. We could also do this only with CSS but I like using the Ionic grid for styling as far as possible.

With some additional styling added to both our and other people’s messages the chatroom will look almost like iMessages or any familiar chat application, so open your src/pages/chat-room/chat-room.html and insert:

<ion-header>
  <ion-navbar>
    <ion-title>
      Chat
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content>
  <ion-grid>
    <ion-row *ngFor="let message of messages">

      <ion-col col-9 *ngIf="message.from !== nickname" class="message" [ngClass]="{'my_message': message.from === nickname, 'other_message': message.from !== nickname}">
        <span class="user_name">{{ message.from }}:</span><br>
        <span>{{ message.text }}</span>
        <div class="time">{{message.created | date:'dd.MM hh:MM'}}</div>
      </ion-col>

      <ion-col offset-3 col-9 *ngIf="message.from === nickname" class="message" [ngClass]="{'my_message': message.from === nickname, 'other_message': message.from !== nickname}">
        <span class="user_name">{{ message.from }}:</span><br>
        <span>{{ message.text }}</span>
        <div class="time">{{message.created | date:'dd.MM hh:MM'}}</div>
      </ion-col>

    </ion-row>
  </ion-grid>

</ion-content>

<ion-footer>
  <ion-toolbar>
    <ion-row class="message_row">
      <ion-col col-9>
        <ion-item no-lines>
          <ion-input type="text" placeholder="Message" [(ngModel)]="message"></ion-input>
        </ion-item>
      </ion-col>
      <ion-col col-3>
        <button ion-button clear color="primary" (click)="sendMessage()" [disabled]="message === ''">
        Send
      </button>
      </ion-col>
    </ion-row>
  </ion-toolbar>
</ion-footer>

Below our messages we also have the footer bar which holds another input to send out messages, nothing really fancy. To make the chat finally look like a chat, add some more CSS to your src/pages/chat-room/chat-room.scss:

page-chat-room {
    .user_name {
        color: #afafaf;
    }
    .message {
        padding: 10px !important;
        transition: all 250ms ease-in-out !important;
        border-radius: 10px !important;
        margin-bottom: 4px !important;
    }
    .my_message {
        background: color($colors, primary) !important;
        color: #000 !important;
    }
    .other_message {
        background: #dcdcdc !important;
        color: #000 !important;
    }
    .time {
        color: #afafaf;
        float: right;
        font-size: small;
    }
    .message_row {
        background-color: #fff;
    }
}

Now launch your app and make sure your backend is up and running!

For testing, you can open a browser and another incognito browser like in my example at the top to chat with yourself.

Conclusion

Don’t be scared of Socket.io and a Node backend, you can easily implement your own realtime backend connection without any problems! Firebase seems often like the easy alternative, but actually we were able to build a live chat app with only a super small backend.

To make this a real Ionic chat you might want to add a database and store all the messages once you receive them and add some routes to return the history of a chatroom. Or you might want to create different chatrooms and separate conversations, but this post could be the starting point to your own chat implementation!

You can watch a video version of this article below.