AngularJS Chatroom Tutorial: No Database or Sockets Required

Comments 8 Standard

So I’ve been playing around with Angular lately and I decide to try my hand at making an Angular chat room since angular is great when it comes to keeping the page display current as the DOM changes. In order to deal with the message sending (and for the ease of getting a working version going) I decided to use PubNub which handles all the message transport quickly and easily.

1. Create A PubNub Account

In order to use this you’ll need to sign up for a PubNub account. There is a free plan and several paid for plans available for you to choose from. Once your account is created you’ll need to find your subscribe key and publish key.

2. Create the chat file

Now you’re going to create the main chat file. This will display your chat room. At the top of the file replace the subscribe_key and publish_key with the one you see in your newly created account.

index.html

<!doctype html>
<html ng-app="angular_chat">
  <head>
    <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
    <script src="http://code.jquery.com/jquery-1.9.1.js"></script>
    <script src="http://angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.4.0.js"></script>
    <div pub-key="PUB_KEY_GOES_IN_HERE" sub-key="SUBSCRIBE_KEY_GOES_IN_HERE" ssl="off" origin="pubsub.pubnub.com" id="pubnub"></div>
    <script src="http://cdn.pubnub.com/pubnub-3.1.min.js"></script>
    <script src="angular-chat.js"></script>
    <link rel="stylesheet" href="style.css" rel="stylesheet">
    <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet">
  </head>
  <body>
  <div class='container-fluid' ng-controller="chatCtrl">
    <div ng-include="'chatHeader.html'"></div>
    <div ng-Show="!loggedIn" id="login">
      <h2>Login</h2>
      <label for="username">
        <b>Username:</b>
      </label>
      <label>
        <input type="text" ng-model="message.username" />
      </label>
      <span ng-click="attemptLogin()">
        <i></i> Go Chat
      </span>
    </div>
    <div ng-Show="loggedIn" id="chat">
      <table>
        <tr ng-repeat="chat in chatMessages">
            <td style="width: 30%;">
              <b>{{chat.username}}</b><br/>
              <small>
               {{chat.date}} @ {{chat.time}}
              </small>
            </td>
            <td colspan="2">{{chat.text}}</td>
        </tr>
        <tr ng-show="chatMessages.length == 0">
          <td colspan="3">No messages yet!</td>
        </tr>
      </table>
    </div>
    <form ng-Show="loggedIn" ui-keypress="{13:'postMessage()'}">
      <div id="inputMessage">
        <input type="text" placeholder="Say hello!" ng-model="message.text" />
        <span ng-click="postMessage()">
          <i></i> Post
        </span>
      </div>
    </form>
    <div ng-include="'chatFooter.html'"></div>
  </div>
  </body>
</html>

3. Create the angular file

Now we’re going to add in a file for our angular scripts. At the top of the file you’ll find some config variables which you can change as you see fit to set the default username, how many messages to show at a time, and the name of the chat channel you’ll be connecting to.

angular-chat.js

/***
 * File: angular-chat.js
 * Author: Jade Krafsig
 * Source: design1online.com, LLC
 * License: GNU Public License
 ***/

/***
 * Purpose: load bootstrap ui angular modules
 * Precondition: none
 * Postcondition: modules loaded
 ***/
angular.module('angular_chat', ['ui.bootstrap']);

/***
 * Purpose: load the existing chat logs
 * Precondition: none
 * Postcondition: chat logs have been loaded
 ***/
function chatCtrl($scope, $http) { 

  /***
   * Configurable global variables
   ***/
  $scope.chatChannel = "angular_chat";
  $scope.messageLimit = 50;
  $scope.defaultUsername = "Guest";

  /***
   * Static global variables
   ***/
  $scope.loggedIn = false;
  $scope.errorMsg;
  $scope.realtimeStatus = 0;

  /***
   * Purpose: clear the message object
   * Precondition: none
   * Postcondition: message object has been reset
   ***/
  $scope.clearMsg = function() {
    $scope.message = {
      username: $scope.defaultUsername,
      email: 'n/a',
      text: ''
    };
  }

  $scope.clearMsg();

  /***
   * Purpose: load the existing chat logs
   * Precondition: none
   * Postcondition: chat logs have been loaded
   ***/
  $scope.chatLogs = function() {
    PUBNUB.history( {
      channel : $scope.chatChannel,
      limit   : $scope.messageLimit
    }, function(messages) {
      // Shows All Messages
      $scope.$apply(function(){
        $scope.chatMessages = messages.reverse();          
      }); 
    });
   }

  /***
   * Purpose: load the existing chat logs
   * Precondition: none
   * Postcondition: chat logs have been loaded
   ***/
   $scope.attemptLogin = function() {
    $scope.errorMsg = "";

    if (!$scope.message.username) {
      $scope.errorMsg = "You must enter a username.";
      return;
    }

    if (!$scope.realtimeStatus) {
      $scope.errorMsg = "You're not connect to PubNub.";
      return;
    }

    $scope.loggedIn = true;
   }

  /***
   * Purpose: remove error message formatting when the message input changes
   * Precondition: none
   * Postcondition: error message class removed from message input
   ***/
  $scope.$watch('message.text', function(newValue, oldValue) {
    if (newValue)
      $("#inputMessage").removeClass("error");
      $scope.errorMsg = "";
  }, true);

  /***
   * Purpose: trying to post a message to the chat
   * Precondition: loggedIn
   * Postcondition: message added to chatMessages and sent to chatLog
   ***/
  $scope.postMessage = function() {

    //make sure they are logged in
    if (!$scope.loggedIn) {
      $scope.errorMsg = "You must login first.";
      return;
    }

    //make sure they enter a chat message
    if (!$scope.message.text) {
      $scope.errorMsg = "You must enter a message.";
      $("#inputMessage").addClass("error");
      return;
    }

    //set the message date
    d = new Date();
    $scope.message.date = d.getDay() + "/" + d.getMonth() + "/" + d.getFullYear();
    $scope.message.time = d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds();

   PUBNUB.publish({
      channel : $scope.chatChannel,
      message : $scope.message
    });

    $scope.message.text = "";
  };

  /***
   * Purpose: connect and access pubnub channel
   * Preconditions: pubnub js file init
   * Postconditions: pubnub is waiting and ready
   ***/
  PUBNUB.subscribe({
    channel    : $scope.chatChannel,
    restore    : false, 
    callback   : function(message) { 
      //update messages with the new message
      $scope.$apply(function(){
        $scope.chatMessages.unshift(message);          
      }); 
    },

    error      : function(data) {
      $scope.errorMsg = data;
    },

    disconnect : function() {   
      $scope.$apply(function(){
        $scope.realtimeStatus = 0;
      });
    },

    reconnect  : function() {   
      $scope.$apply(function(){
        $scope.realtimeStatus = 1;
      });
    },

    connect    : function() {
      $scope.$apply(function(){
        $scope.realtimeStatus = 2;
        //load the chat logs
        $scope.chatLogs();
      });
    }
  });

  /***
   * Purpose: trying to post a message to the chat
   * Precondition: loggedIn
   * Postcondition: message added to chatMessages and sent to chatLog
   ***/
  $scope.attemptLogout = function() {
    $("#inputMessage").removeClass("error");
    $scope.clearMsg();
    $scope.loggedIn = false;
  }
}

4. Create the header and footer files

These files will put a static header and footer on your chat room.

chatHeader.html

<div>
  <h1>Angular Chat</h1>
  <h5><i></i> using Bootstrap and PubNub</h5>
  <p>&nbsp;</p>
  <div ng-show="realtimeStatus == 0">
    <span><i></i> Disconnected</span>
  </div>
  <div ng-show="realtimeStatus == 1">
    <span><i></i> Connecting...</span>
  </div>
  <div ng-show="realtimeStatus == 2">
    <span><i></i> Connected</span>
  </div>
  <div ng-show="loggedIn" id="logout">
    <span ng-click="attemptLogout()"><i></i> Logout</span>
  </div>
</div>
<p>&nbsp;</p>
<div ng-show="errorMsg">
  <i></i> <b>Error:</b> {{errorMsg}}
</div>

chatFooter.html

<div>
  2013 &copy; design1online.com
</div>

5. Create the stylesheet

This will adjust the height of the chat window and the chat header status and logout buttons.

style.css

#chat {
 height: 33em; 
 overflow: auto;
 background-color: #EEE;
 border-radius: .25em;
 margin-bottom: 1em;
}

.status {
  float: left;
  margin-top: -1em;
}

#logout {
  float: right;
  margin-top: -1.2em;
}

6. Try the working version

See what the chat room looks like on my plunkr account or fork it on GitHub.

Angular Js

Leave a comment Standard

I wrote a few plunkers this past month that I thought I would share for anyone whose interested in dabbling in Angular JS. Enjoy!