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:
Add a Hub class to this project and add the following code to it:
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:
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:
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:
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.
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!
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!
This article is excellent, it will serve me, thank you for sharing
ReplyDeleteYou're welcome Marc :)
DeleteI'm getting a 'SignalR: Connection has not been fully initialized.' - any ideas?
ReplyDeleteI 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
DeleteCheers Ravi
ReplyDeleteI fixed it by adding .done() to the start() method, but your post is better, thanks
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.
ReplyDeleteJonathan,
DeleteI 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?
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).
ReplyDeleteYes. 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
DeleteHi Ravi,
ReplyDeleteFirst 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
Hi Abbas,
DeleteAre 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.
Hi Ravi,
ReplyDeletethanks 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,
Thanks for help, It works when we added global.asax as
ReplyDelete----
protected void Application_Start(object sender, EventArgs e)
{
RouteTable.Routes.MapHubs();
}
-------
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 :)
DeleteRavi, 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?
ReplyDeleteThank you in advance,
Max.
This comment has been removed by the author.
ReplyDeleteGREAT, Thanks, really what i was looking for, recently noticed interesting posts Ravi, good job
ReplyDeletegood
ReplyDeleteHi Ravi,
ReplyDeleteI can't able to call SignalR hub methods,if i use AngularJS routing to load the view.
from where i can download the code?
ReplyDeletei got Error 1 The name 'RouteTable' does not exist in the current context
ReplyDeletei have included using Microsoft.AspNet.SignalR;
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