SignalR with MVC 5, AngularJS and Web API

Tags: SignalR, MVC, ASP.NET, WebAPI, Angular

In my last post I walked through an ASP.NET MVC 5 solution that used SignalR, Web API, and Knockout for data binding. I said that my next stop was AngularJS. So now I want to compare and contrast Knockout with Angular. This post is going to revisit that solution using AngularJS instead of Knockout.

First things first. As John Papa wrote Knockout is a javascript data-binding library while Angular is an entire presentation framework. So my comparison is not strictly apples to apples. For my present purposes however I'm only interested in the Angular core data binding functionality rather than the goodness that's in the rest of the framework. So my comparison is solely between two-way data binding in both Knockout and AngularJS.

The code for this solution is checked into github here. If you want to walk through the process of creating the solution, or just want to dive into the details, here are the steps:

Solution Setup

(1) Set up the solution by creating a new MVC 5 project in Visual Studio 2013. Be sure to check the Web API box to add core references for it. Also choose "No authentication" to keep things simple. I called my solution "SignalRAngularDemo" but feel free to call it whatever you want.

(2) Add SignalR 2.0.2 and Angular Core 1.2.10 nuget packages to the solution.

(3) Add the references for SignalR and AngularJS to BundleConfig.cs:

bundles.Add(new ScriptBundle("~/bundles/signalr").Include(
    "~/Scripts/jquery.signalR-*"
));

bundles.Add(new ScriptBundle("~/bundles/angular").Include(
    "~/Scripts/angular.js"
));

(4) In _Layout.cshtml tell MVC to render the scripts:

@Scripts.Render("~/bundles/signalr")
@Scripts.Render("~/bundles/angular")

The next few steps are exactly the same as in the previous post so I won't repeat them in detail here. In order:

  • Bootstrap SignalR and create the SignalRBase and WidgetHub classes beneath the Hubs folder.
  • Add the WidgetController Web API class under the Controllers folder.
  • Add the Widget class under the Models folder.
  • Add the two views Sender.cshtml and Receiver.cshtml as before.

Views (Sender and Receiver)

The Sender.cshtml view is identical to the previous post's solution. When you add a new widget it still makes an ajax call to the WidgetController API and posts the new widget data. One of my design goals in these two demos was to have the sender call a Web API rather than have knowledge of the SignalR hub (as with so many online tutorials). So just copy and paste the code from the other demo's Sender page to this one.

The Receiver.cshtml page is going to be different, however. That's because it uses Angular for its data binding. Replace its HTML with this code:

@{
    ViewBag.Title = "Receiver";
}

<h2>Receiver</h2>

<p>This view subscribes to the WidgetHub</p>

<div data-ng-controller="WidgetCtrl">
    <ul>
        <li data-ng-repeat="widget in widgets">
            ID {{widget.ID}}: {{widget.Color}} {{widget.Shape}}
        </li>
    </ul>
</div>

@section Scripts {
    <!-- Reference the autogenerated SignalR hub script -->
    <script src="~/signalr/hubs"></script>
}

Bootstrapping Angular 

Notice that the old Knockout data binding syntax is gone and replaced by the curly brace syntax of Angular expressions. You can run the app at this point but there's no data binding (yet). Similar to Knockout, Angular data-binding uses an MVC pattern. So Receiver is the "V" in "MVC" but we need to create a model and a controller that passes the model to the view. See the introduction if you're brand spankin' new to Angular. Here's the Angular architecture from a high level:

Now let's write the Angular client-side code to support the Receiver view. We will also need Angular to work with SignalR. More on that in a bit.

The first thing we need to do is bootstrap Angular upon startup. In _Layout.cshtml replace the html tag with the data attribute below. Notice that I prepend the standard ng-app directive with data-ng-app in order to make the HTML valid:

<html data-ng-app="app">

Next following the convention set up by John Papa set up a folder called app in the project root. Below it add folders called controllers and services. Finally, add a javascript file called app.js to the app root. In app.js declare the Angular app:

var app = angular.module('app', []);
app.value('$', $);

Supporting SignalR in an Angular Service

The next task is to support SignalR in Angular. As you'll recall in the previous Knockout demo the hub connection was done right in the jQuery startup function for the Receiver view. That's not ideal. It would be better to decouple the hub from the views that connect to it. Angular has the idea of a singleton object called an "Angular Service" that can encapsulate common tasks for reuse by other Angular components. For example, I can create a service called "fibSvc" that returns n number of integers following the Fibonacci sequence. The fibSvc can be constructor-injected into any controller and used like any other object passed into a C# object.

I think you see where I'm going with this. Wouldn't it be cool to have a service that encapsulated the SignalR hub connections for us? Well here's a shout out to Ravi Kiran who wrote just such a service. I've adapted his code for my use in this demo:

app.service('widgetSvc', function ($, $rootScope) {
    var proxy = null;

    var initialize = function () {

        // fetch connection object and create proxy
        var connection = $.hubConnection();
        this.proxy = connection.createHubProxy('widgets');

        // start connection
        connection.start();

        this.proxy.on('newWidget', function (data) {
            $rootScope.$emit("newWidget", data);
        });
    };

    var sendRequest = function () {
        // invoke sendWidget method defined in hub
        this.proxy.invoke('sendWidget');
    };

    return {
        initialize: initialize,
        sendRequest: sendRequest
    };
});

Add this service to a file called widgetSvc.js beneath the /app/services folder.

Widget Controller

Next we need a controller to pass the model to the view (Receiver.cshtml). Add this code to a file called widgetCtrl.js in the /app/controllers folder.

function WidgetCtrl($scope, widgetSvc, $rootScope) {
    $scope.widgets = new Array();

    $scope.sendWidget = function() {
        widgetSvc.sendRequest();
    };

    var addWidget = function(data) {
        $scope.widgets.push(data);
    };

    widgetSvc.initialize();

    $scope.$parent.$on("newWidget", function (e, data) {
        $scope.$apply(function () {
            addWidget(data);
        });
    });
}

Notice that the widgetSvc object (i.e., javascript function) is contructor-injected into the widgetCtrl object. We don't have to think about DI because Angular takes care of that for us. An Angular controller is a funny thing. It looks to me more like a ViewModel at times. That's because it contains objects belonging to the $scope. In this case it has a collection of widgets that are initialized to an empty array. Anytime the hub newWidget dynamic method is invoked the widgetCtrl addWidget gets called and a new widget gets added to the array.

Additional Bundling

You do need to bundle up your scripts that live beneath the app folder so they get sent down to the client. And don't forget to add the bundle to the _Layout.cshtml page:

bundles.Add(new ScriptBundle("~/bundles/widgetApp").Include(
    "~/app/app.js",
    "~/app/services/widgetSvc.js",
    "~/app/controllers/widgetCtrl.js"
));

Now take a look at the Receiver.cshtml view again. The div tag tells Angular to use the WidgetCtrl class and an ng-repeat expression (similar to Knockout's foreach statement) will iterate over the widgets array and create a list item for each one.

Fire It Up

Do the same test as in the previous demo with Knockout. Here I've got one sender browser instance and one receiver browser instance:

Final Thoughts

I've heard some people say that Angular is a bigger learning curve than Knockout. Maybe. But it seems to me that once you get beyond the learning curve of either tool they are about the same. For me because I was very familiar with Knockout it was pretty easy to port my experience over to the Angular way of doing things. I do like the curly-brace syntax of Angular better than the verbose data-bind syntax of Knockout. The biggest bonus it seems is you don't have to worry about wiring up observables and observableArrays like you did with Knockout. Angular takes care of that behind the scenes for you.

The bottom line is I'll probably continue to use Knockout for simple apps but if I suspect that my project will grow in complexity over time then I'll probably want to use Angular instead. For my purposes here I merely wanted to know that SignalR works well with both Knockout and Angular. I have satisfied my curiosity with respect to that question.