Saturday, July 12, 2014

Angular JS and signalR Based Chat App

Hello friends! Here I’m writing useful blog for those programmers who are going to start learning Angular JS, and also for those who want make chat application using SignalR.
So here I'm creating a sample chat application using bootstrap, SignalR and Angular JS in .net framework 4.0.
Here I’m going to explain some part of this chat application. Let start from creating SignalR hub class which is play vital role for handling bi-directional connection between server to client and client to server.


Hub class




using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
namespace AngularChatApp.ChatController
{
    public class Chat : Hub, IChat
    {
        private static List<User> userCollection = new List<User>();
        private static List<MapRoomUser> mapRoomUserCollection = new List<MapRoomUser>();
        private static List<Message> msgCollection = new List<Message>();

        public void EnterRoom(string userName, string roomId)
        {
            var users = userCollection.Where(s => s.UserName == userName);
            if (users.Count() == 0)
                userCollection.Add(new User() { UserName = userName, ConnectionId = new string[] { Context.ConnectionId } });
            else
                users.FirstOrDefault().ConnectionId = new string[] { Context.ConnectionId };
            if (mapRoomUserCollection.Where(s => s.user.UserName == userName && s.roomId == s.roomId).Count() == 0)
                mapRoomUserCollection.Add(new MapRoomUser() { user = new User() { UserName = userName }, roomId = roomId });
            Groups.Add(Context.ConnectionId, roomId);
            LoadChatHistory(roomId, userName);
        }

        public void SendRoomMessage(string userName, string roomId, string message)
        {
            var msg = new Message() { SendUser = userName, RoomId = roomId, MessageId = Guid.NewGuid().ToString(), Text = message };
            msgCollection.Add(msg);
            Clients.Group(roomId).getRoomMessage(msg);
        }

        public void LeaveRoom(string userName, string roomId)
        {
            mapRoomUserCollection.Remove(mapRoomUserCollection.Where(s => s.user.UserName == userName && s.roomId == roomId).FirstOrDefault());
        }

        public void LoadChatHistory(string roomId, string userName)
        {
            var messages = msgCollection.Where(s => s.RoomId == roomId);
            Clients.Client(Context.ConnectionId).showChatHistory(messages);
        }
    }
}

ng-view Container

After creating chat hub class, lets come on view side of chat application. Here we need a page where we bind ng-view for displaying room list, chat history at client side. For this I added a chat.aspx page and mention ng-view attribute into the div element. We also need to mention app name which is required for route and controller configuration.


Chat.aspx

 <div ng-app="ChatApp">
        <div ng-view>
        </div>
 </div>

After mention ng-app and ng-view. Now, lets create the config js for configuration of angular route and controller. A controller is for handling view of room list and chat  history, and factory has all client side method of signalR.


Config.js

var app = angular.module("ChatApp", ['ngRoute']);
app.config(function ($routeProvider) {
    $routeProvider
    .when('/', {
        controller: 'Rooms',
        templateUrl: 'Template/Rooms.htm'
    })
    .otherwise({
        redirectTo: '/'
    });
});

Factory.js

app.factory("RoomFactory", function () {
    var obj = {
        RoomId: 0,
        Rooms: [{ UnReadMsgCount: 0, RoomName: "Room1", RoomId: 1 },
                    { UnReadMsgCount: 0, RoomName: "Room2", RoomId: 2 },
                    { UnReadMsgCount: 0, RoomName: "Room3", RoomId: 3 },
                    { UnReadMsgCount: 0, RoomName: "Room4", RoomId: 4 },
                    { UnReadMsgCount: 0, RoomName: "Room5", RoomId: 5 },
                    { UnReadMsgCount: 0, RoomName: "Room6", RoomId: 6}]
    }

    
    return obj;
});

app.factory("signalR", function () {
    var $hub = $.connection.chat;
    var signalR = {
        startHub: function () {
            $.connection.hub.start();
        },
        EnterRoom: function (username, roomid) {
            $hub.server.enterRoom(username, roomid);
        },
        SendRoomMessage: function (userName, roomId, message) {
            $hub.server.sendRoomMessage(userName, roomId, message);
        },
        GetRoomMessage: function (callback) {
            $hub.client.getRoomMessage = callback;
        },
        RequestChatHistory: function (roomId, userName) {
            $hub.server.loadChatHistory(roomId, userName);
        },
        GetChatHistory: function (callback) {
            $hub.client.showChatHistory = callback;
        }
    }
    return signalR;
});

Contoller.js

app.controller("Rooms", function ($scope, RoomFactory, signalR) {
    $scope.$parent.UserName = "";
    $scope.rooms = RoomFactory.Rooms;
    $scope.$parent.UserName = prompt("Enter unique name :");
    $scope.chatHistory = [];
    signalR.startHub();
    $scope.typemsgdisable = true;
    $scope.openChatPanel = function (RoomId, RoomName) {
        //$scope.templateURL = "/Template/ChatPanel.htm";
        RoomFactory.RoomId = RoomId;
        $scope.RoomId = RoomId;
        $scope.RoomName = RoomName;
        $scope.chatHistory = [];
        signalR.EnterRoom($scope.$parent.UserName, $scope.RoomId);
        $scope.typemsgdisable = false;
        //Check this for each function
        angular.forEach($scope.rooms, function (room, key) {
            if ($scope.RoomId == room.RoomId) {
                room.UnReadMsgCount = 0;
            }
        });
        $scope.$apply();
    };
    $scope.sendMsgOnEnter = function ($event) {
        if ($event.keyCode == 13) {
            $scope.sendMessage();
            $event.preventDefault();
        }
    }
    $scope.sendMessage = function () {
        signalR.SendRoomMessage($scope.$parent.UserName, $scope.RoomId, $scope.typeMessage);
        $scope.typeMessage = "";
    };
    signalR.GetRoomMessage(function (message) {
        if (message.RoomId == $scope.RoomId) {
            $scope.chatHistory.push(message);
            $scope.$apply();
        }
        else {
           
            angular.forEach($scope.rooms, function (room,key) {
                if (message.RoomId == room.RoomId) {
                    room.UnReadMsgCount = room.UnReadMsgCount + 1;
                }
            });
            $scope.$apply();
        }
    });
    signalR.GetChatHistory(function (messages) {
        $scope.chatHistory = messages;
        $scope.$apply();
    });
});

And Last one is Templates.

Rooms.html


<div class="container theme-showcase">
    <div class="jumbotron-custom">
        <div style="text-align: center">
            <h2>
                User Name : {{UserName}}
            </h2>
        </div>
        <div class="row">
            <div class="col-md-3">
                <ul class="list-group">
                    <li class="list-group-item" ng-repeat="room in rooms" ng-click="openChatPanel(room.RoomId,room.RoomName);">
                        <span class="badge">{{room.UnReadMsgCount}}</span> {{room.RoomName}} </li>
                </ul>
            </div>
            <div class="col-md-8">
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <h3 class="panel-title">
                            {{RoomName}}</h3>
                    </div>
                    <div class="panel-body" style="height: 500px">
                        <!--<table class="table">
                            <thead>
                                <tr>
                                    <th>
                                        Sender User
                                    </th>
                                    <th>
                                        Message
                                    </th>
                                </tr>
                            </thead>
                            <tbody>
                                <tr ng-repeat="chat in chatHistory">
                                    <td>
                                        {{chat.SendUser}}
                                    </td>
                                    <td>
                                        {{chat.Text}}
                                    </td>
                                </tr>
                            </tbody>
                        </table>-->
                        <div ng-repeat="chat in chatHistory">
                            <div class="userName">
                                {{chat.SendUser}}</div>
                            <div class="message ">
                                <div class="message-inner">
                                    {{chat.Text}}</div>
                            </div>
                        </div>
                    </div>
                    <div class="input-group">
                        <input id="txtMessage" type="text" class="form-control" ng-model="typeMessage" ng-keypress="sendMsgOnEnter($event)"
                            ng-disabled="typemsgdisable" />
                        <span class="input-group-btn">
                            <button id="btnsend" class="btn btn-default" type="button" ng-click="sendMessage();"
                                ng-disabled="typemsgdisable">
                                Send</button>
                        </span>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

After Complete all the things your app will look be like as below.


Click Here for get the source code.

NuGet Information:

Angular Nuget Command : PM> Install-Package angularjs -Version 1.2.19
BootStrap Nuget Command : PM> Install-Package bootstrap
SignalR Nuget Command : PM> Install-Package bootstrap


Note:  This is my first blog of my life. I'm not good at English, so please take care of this.

13 comments:

  1. Hi, I'm trying to use your project but I have o problem: I cannot connect more then 5 users in same time on application. You know why?

    ReplyDelete
    Replies
    1. Check here screen shots, I checked it with five user it working for me.
      https://www.dropbox.com/s/cryodoreq73qu3s/5UserChatLogged.jpg?dl=0
      Have you changed any code in this project?

      Delete
  2. I know is working with five, but not with six. I tested your original code.

    ReplyDelete
    Replies
    1. I test it with 7 user and its working for me.

      Delete
    2. If you could manage user into db instead of static list in hub class then it could solve your problem.

      Delete
  3. I don't think this is the problem. I will check if this is true:
    http://forums.asp.net/p/2039166/5875383.aspx?Concurrent+connections+problem+in+signalR+hub

    ReplyDelete
  4. I verified the answer from forum.asp.net, and it's true: I can open five connection in Chrome, and I can open another five connection in IE, that mean the concurrent connections number in same browser it's limited.

    ReplyDelete
  5. Thanks for sharing information.

    ReplyDelete
  6. Hi, I think you have a mistake in Controller.js. line:

    signalR.startHub();

    I think must be after you declare all callbacks in signalR.

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. I think it couldn't make any issue. If communication direction is happening from client to server then callbacks can be call after or before starting hub. If communication is happening from server to client then those callbacks should be call after starting hub. So thats why I called signalR.startHub() before all callback.

      Delete
    3. I am used communications in both scenarios, and I get an error if I start hub before declaring the clients callbacks. When I moved the command for start hub after callbacks declarations, the error message vanish.

      Delete