Sunday, 19 May 2013

Hooking up ASP.NET SignalR with Angular JS

ASP.NET SignalR is a great framework to do real-time communication. I love this framework and I wrote some posts on it in the past. On the other hand, we have Angular JS, the super-heroic JavaScript library to build rich web applications. How about using these frameworks together to build an application with data getting pushed from server continuously and binding it on UI with nicely architected JavaScript code? It definitely sounds great. Let’s see it in action.

In this post, we will create a simple Hello World kind of application using SignalR and Angular JS. We will look at a more advanced scenario in a future post.

Creating Hub
Open Visual Studio (2010 or 2012) and create a new web application. Add SignalR’s NuGet package to this project. Add SignalR route to the Application_Start event of Global.asax as shown below:

protected void Application_Start(object sender, EventArgs e)
{
    RouteTable.Routes.MapHubs();
}

Add a Hub class to this project and add the following code to it:
public class HelloWorldHub : Hub
{
    public void GreetAll()
    {
        Clients.All.acceptGreet("Good morning! The time is " + DateTime.Now.ToString());
    }
}


On client side
A client can communicate with hub either using a proxy script generated by SignalR or without using proxy. I personally like the no-proxy approach, as it is easier to take control over certain things when we follow this approach. In an Angular JS application, it is always recommended to wrap calls to data sources in a separate service. In such service, we need to take control to add callbacks easily. For this reason, we will follow the no-proxy approach. Following are the scripts required on the page:


<script type="text/javascript" src="../Scripts/jquery-1.9.1.js"></script>
<script type="text/javascript" src="../Scripts/angular.js"></script>
<script type="text/javascript" src="../Scripts/jquery.signalR-1.0.1.min.js"></script>

The page will have a button and a span. When someone clicks on the button, method on the hub will be called to send a greeting message to all clients, which will be displayed on the span. Following is the mark-up on the page:
<div data-ng-app="app" data-ng-controller="SignalRAngularCtrl">
<input type="button" name="GreetAll" value="Greet All" data-ng-click="greetAll()" />
<span>{{text}}</span>
</div>

As stated earlier, the recommended way to call any external data source in Angular JS is using a custom service. We can create a factory or service to solve this purpose. In the following snippet, we create a new module and add a factory performing all SignalR operations to it:

var app = angular.module('app', []);
app.value('$', $);
        
app.factory('signalRSvc', function ($, $rootScope) {
    return {
        proxy: null,
        initialize: function (acceptGreetCallback) {
            //Getting the connection object
            connection = $.hubConnection();
 
            //Creating proxy
            this.proxy = connection.createHubProxy('helloWorldHub');
 
            //Starting connection
            connection.start();
 
            //Attaching a callback to handle acceptGreet client call
            this.proxy.on('acceptGreet', function (message) {
                $rootScope.$apply(function () {
                    acceptGreetCallback(message);
                });
            });
        },
        sendRequest: function (callback) {
            //Invoking greetAll method defined in hub
            this.proxy.invoke('greetAll');
        }
    }
});


Notice the callback passed to initialize method. This callback is a function defined in controller to set data received from server to a scope variable.

Also, notice the way the callback is invoked in proxy.on(). It is wrapped inside $rootScope.$apply(). This is because, the callback of proxy.on() gets executed outside Angular’s realm. Model modification done inside this function will not be processed unless it is called using $apply.

Finally, we need a controller to make everything work together.

function SignalRAngularCtrl($scope, signalRSvc) {
    $scope.text = "";
 
    $scope.greetAll = function () {
        signalRSvc.sendRequest();
    }
 
    updateGreetingMessage = function (text) {
        $scope.text = text;
    }

    signalRSvc.initialize(updateGreetingMessage);
}

As you see, the controller is pretty straight forward. As everything gets kicked off from controller, we need an object of the custom service, which is injected into the controller. The controller invokes the initialize method of the service as soon as it is instantiated. This is to start the connection and configure proxy with required callbacks.

Now open the page created above on two different browsers and click on button in one of the browser. You should be able to see a greeting message with current time.

Note: If you have finished reading this article and want to use your learning in a project, make a note to check this post as well: A Better Way of Using ASP.NET SignalR With Angular JS

Happy coding!

22 comments:

  1. This article is excellent, it will serve me, thank you for sharing

    ReplyDelete
  2. I'm getting a 'SignalR: Connection has not been fully initialized.' - any ideas?

    ReplyDelete
    Replies
    1. I think, you invoked a server method immediately after start statement. If so, I did a blog post about this issue a while back, you can check it here: http://sravi-kiran.blogspot.in/2012/08/calling-signalr-server-function-on-page.html

      Delete
  3. Cheers Ravi

    I fixed it by adding .done() to the start() method, but your post is better, thanks

    ReplyDelete
  4. By putting the initialize with a callback in the controller you're essentially tightly coupling limiting reuse. You should consider using $broadcast on $rootScope so that other controllers can listen for things with $scope.$on('') rather than using the callback like that. Also, your factory is used more like a service, just a global singleton so you might as well make it a service.

    ReplyDelete
    Replies
    1. Jonathan,

      I appreciate the inputs. Calling initialize in the controller even doesn't look good. I think a better way is to use a run block on the module to start the connection. Your opinion?

      Delete
  5. Thanks for the code, thank you for sharing. I had to do one modification to get it to work: call RouteTable.Routes.MapHubs() in Global.asax before RouteConfig.RegisterRoutes(RouteTable.Routes).

    ReplyDelete
    Replies
    1. Yes. You have to register Routes of SignalR before any other Route configurations. It is mentioned in SignalR FAQ as well: https://github.com/SignalR/SignalR/wiki/Faq

      Delete
  6. Hi Ravi,

    First of all thanks for sharing it, I gone through your article and using it for creating a demo application but stuck on above error of "Connection has not been fully initialized", even i tried .done() but not work, may you please share your solution in angular term. i tried your suggested solution but how can we implement into this article.

    Your help much appreciated.

    Thanks

    ReplyDelete
    Replies
    1. Hi Abbas,

      Are you calling sendRequest() immediately after starting the connection? If so, try this approach:

      connection.start().done(sendRequest)

      in the initialize function of the service. If it doesn't solve your issue, drop a mail to me using contact link, I will try to help.

      Delete
  7. Hi Ravi,
    thanks for quick reply,

    I am calling initialize function first and sendRequest on button click.
    yes, I tried your suggestion but it saying "ReferenceError: sendRequest is not defined".

    I gone through your another link as well..but same issue
    http://sravi-kiran.blogspot.in/search/label/SignalR
    ...

    if possible Please add me on your contact and drop me an email on abbas.here@gmail.com

    Thanks,

    ReplyDelete
  8. Thanks for help, It works when we added global.asax as

    ----
    protected void Application_Start(object sender, EventArgs e)
    {
    RouteTable.Routes.MapHubs();
    }
    -------

    ReplyDelete
    Replies
    1. Abbas, Thanks for letting me know. I was expecting people to know SignalR before reading this article. Antonio also commented above about the same issue that you faced. So, I updated the article so that others won't face the issue while following this article :)

      Delete
  9. Ravi, I'm just started learning java script and angularjs. I have a bit expirience with SignalR (for Silverlight app). Could you please share working solution?

    Thank you in advance,

    Max.

    ReplyDelete
  10. This comment has been removed by the author.

    ReplyDelete
  11. GREAT, Thanks, really what i was looking for, recently noticed interesting posts Ravi, good job

    ReplyDelete
  12. Hi Ravi,

    I can't able to call SignalR hub methods,if i use AngularJS routing to load the view.

    ReplyDelete
  13. from where i can download the code?

    ReplyDelete
  14. i got Error 1 The name 'RouteTable' does not exist in the current context

    i have included using Microsoft.AspNet.SignalR;

    ReplyDelete
  15. Error 1 'System.Web.Routing.SignalRRouteExtensions.MapHubs(System.Web.Routing.RouteCollection)' is obsolete: 'Use IAppBuilder.MapSignalR in an Owin Startup class. See http://go.microsoft.com/fwlink/?LinkId=320578 for more details.'

    ReplyDelete

Note: only a member of this blog may post a comment.