How to write real-time web app using SignalR, TypeScript, AngularJS, HTML5?
Nowadays, end-users like responsive applications and there are times when certain type of applications need to support real-time updates if required. In this post, I’ll tell you how to write real-time web app using SignalR, TypeScript, AngularJS, HTML5.
I recently published a similar post that explains how to write real-time web app using SignalR, TypeScript, KnockoutJS & HTML5. Most of the concept is same in both the posts but one uses KnockoutJS and AngularJS.
As such this is a how-to post, I expect readers to have some basic knowledge about these technologies mentioned above. But in a nutshell:
What is SignalR?
ASP.NET SignalR is a new library for ASP.NET developers that makes developing real-time web functionality easy. SignalR allows bi-directional communication between server and client. Servers can now push content to connected clients instantly as it becomes available. SignalR supports Web Sockets, and falls back to other compatible techniques for older browsers. SignalR includes APIs for connection management (for instance, connect and disconnect events), grouping connections, and authorization.
– ASP.NET
What is TypeScript?
Typescript is a typed superset of JavaScript that compiles to plain JavaScript. It runs in any browser, any host and any OS. It is open source with good tooling support for various IDE and code editors.
– Learn more about it here.
What is AngularJS?
AngularJS is a structural framework for dynamic web apps. It lets you use HTML as your template language and lets you extend HTML’s syntax to express your application’s components clearly and succinctly. Angular’s data binding and dependency injection eliminate much of the code you would otherwise have to write. And it all happens within the browser, making it an ideal partner with any server technology.
– AngularJS
Requirement/Scope
A real-time web application can be really exciting and SignalR makes it more interesting by providing strong client support as it can leverage Web Sockets, Server-Sent Events (SSE), Forever Frame and Long Polling whenever needed.
Let us define the scope of the real-time application that will be ready by the end of this post. There is a requirement to show the CPU, paging and disk usage on the server to the users of our web application. This can be part of an internet or intranet web app. The code snippets in this post doesn’t account authentication or authorization to keep the scope simple. Ideally, this kind of information can be a part of app’s dashboard.
Tools/Packages
I’m using Visual Studio for this requirement to create an ASP.NET web application with MVC & Web API and name it as “PerfSurf”. I opted this template type so as to get Bootstrap, jQuery and some other default packages. Make sure you have proper TypeScript support in your IDE with latest Web Essentials update.
Use the Nuget Package Manager to install following packages: AngularJS, SignalR and their nuget packages names are “angularjs”, “Microsoft.AspNet.SignalR” respectively.
To show the real-time updates on the user-interface get Smoothie.js, a charting library. Unfortunately, this is not available via nuget at the time of writing this post. So, visit the link, copy the raw library code and paste it in a new file in your vendor specific scripts folder.
To leverage strong typing support of TypeScript, install following packages: jquery.TypeScript.DefinitelyTyped
,
angularjs.TypeScript.DefinitelyTyped
,
signalr.TypeScript.DefinitelyTyped
, smoothie.TypeScript.DefinitelyTyped
. These are the definition files that helps an IDE to give proper intellisense support.
Server-side Setup
Based on your authentication type – Internet or Intranet, you may or may not have a Startup.cs
file in your project at root level. If it is not there, add this file and configure the app to use SignalR. My project is internet type so I already had this file and it looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
using Microsoft.Owin; using Owin; [assembly: OwinStartupAttribute(typeof(PerfSurf.Startup))] namespace PerfSurf { public partial class Startup { public void Configuration(IAppBuilder app) { app.MapSignalR(); ConfigureAuth(app); } } } |
The method named MapSignalR
maps SignalR hubs to the app builder pipeline at /signalr
.
Hub
The next main step is to create a custom Hub that derives from Microsoft.AspNet.SignalR.Hub
. It provides methods that communicate with SignalR connections that is connected to a hub. This hub will basically define all the methods (marked public) that clients (browser) can use. Below is the code to create a hub for our requirement:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
namespace PerfSurf.Hubs { public class PerfHub : Hub<IPerfHubClient> { public PerfHub() { StartCounterCollection(); } private void StartCounterCollection() { var task = Task.Factory.StartNew(async () => { var perfService = new PerfCounterService(); while (true) { var results = perfService.GetResults(); Clients.All.NewCounters(results); await Task.Delay(2000); } }, TaskCreationOptions.LongRunning); } } } |
The code statement Clients.All.NewCounters(results);
instructs SignalR to invoke a JavaScript function named newCounters()
on every connected client and pass results
object as parameter to it. The All
member is a dynamic object which allow us to add any name. In the later version of SignalR, a new feature was added which allows a custom Hub to inherit from the base hub of specific type where all the members defined in this type is supposed to be implemented in JavaScript/client code.
So, defining a IPerfHubClient
interface also provides us intellisense support in IDE otherwise there will be no support as All
is a dynamic type. It’s definition is shown below:
1 2 3 4 5 6 7 8 |
namespace PerfSurf.Hubs { public interface IPerfHubClient { [HubMethodName("newCounters")] void NewCounters(object results); } } |
HubMethodName
attribute on member instructs SignalR to convert pascal-case to camel-case when it creates the client-side methods on the fly for consumption. This is handy as writing a method name in camel-case on server-side doesn’t look right!
Let us look at StartCounterCollection
implementation. We want our hub to start sending the counter values as soon as possible. So, we just start a new task which runs a while loop but with some delay. It consumes a custom counter service named PerfCounterService
to get the results of CPU, Paging and Disk usage of server which is then sent to all the connected client using Clients.All.NewCounters(results)
. Note that we also instruct that this task is a long-running task with the help of TaskCreationOptions
.
PerfCounterService
is a simple class that just setups the counters that the client needs as per the requirement of our application. It is good to keep this separate so that this counters can be added or removed as and when required without affecting the rest of the application. The definition of this class is as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
namespace PerfSurf.Counters { public class PerfCounterService { public PerfCounterService() { _counters = new List<PerfCounterWrapper>() { // CPU usage new PerfCounterWrapper("Processor", "Processor", "% Processor Time", "_Total"), // Paging on the server new PerfCounterWrapper("Paging", "Memory", "Pages/sec"), // Disk usage new PerfCounterWrapper("Disk", "PhysicalDisk", "% Disk Time", "_Total") }; } List<PerfCounterWrapper> _counters; public dynamic GetResults() { return _counters.Select(c => new { name = c.Name, value = c.Value }); } } } |
It stores a list of counters to be used in app and GetResults
method just returns a friendly name and a value for counter. It uses a custom counter wrapper type – the definition is mentioned below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
namespace PerfSurf.Counters { public class PerfCounterWrapper { private PerformanceCounter _counter; public PerfCounterWrapper(string name, string category, string counter, string instance = "") { Name = name; _counter = new PerformanceCounter(category, counter, instance, readOnly: true); } public string Name { get; set; } public float Value { get { return _counter.NextValue(); } } } } |
The above class is a wrapper for System.Diagnostics.PerformanceCounter
to define a friendly name, category, counter, instance for a counter. Note that all counter have different values for category, counter or instance. Look at the PerfCounterService
to check what is required for different required counters.
Client-side Setup
As such this project is using ASP.NET MVC, we will next modify the Home/Index.cshtml
view to have following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
@{ ViewBag.Title = "Real-time web app with AngularJS"; } <div data-ng-app="app"> <script type="text/ng-template" id="embedded.home.html"> <div class="row" data-ng-repeat="c in vm.counters track by $index"> <div class="col-md-12"> <h2 data-ng-bind="c.name"></h2> <canvas width="800" height="100" data-ng-attr-id="{{c.name}}"></canvas> </div> </div> </script> <div data-ng-view></div> </div> @section scripts{ <script src="~/Scripts/jquery.signalR-2.2.0.js"></script> <script src="~/Scripts/angular.js"></script> <script src="~/Scripts/angular-route.js"></script> <script src="~/Scripts/angular-sanitize.js"></script> <script src="~/Scripts/smoothie.js"></script> <script src="~/ngapp/ChartEntry.js"></script> <script src="~/ngapp/app.module.js"></script> <script src="~/ngapp/home.controller.js"></script> <script src="~/ngapp/signalr.service.js"></script> } |
Code from line no. 19-23 reference all the third-party libraries and no. 25-28 reference our app specific code. Line 6-13 uses a script of type template so as to keep the home view close to master view. The id/reference is used by angular to glue up controller and view that is defined in the routing configuration (check code snippet below).
Code from line 7-12 uses Angular’s data-ng-repeat
binding to render the structure for all the counters defined in the model. We have 3 counters as per requirement. It then creates a canvas element for each counter with a unique id using counter’s name. This makes sure that our app is ready to support any number of counters sent from the server. Also this canvas is required to use Smoothie chart library so that it can stream data on it.
Next important step is to define an angular module in app.module.ts
and the definition is mentioned below. It also defines a route which addresses the template in the code above and a controller.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
module app { angular.module('app', ['ngRoute']) .config(function ($routeProvider) { $routeProvider. when('/home', { templateUrl: 'embedded.home.html', controller: 'HomeController as vm' }). otherwise({ redirectTo: '/home' }); }) //reference the instance of related lib so we can inject them to the controllers/services in an angular way. .factory('$', [ '$window', $window => $window.jQuery ]); } |
The definition for home controller is below. I also have a post that describes how to write AngularJS controller using TypeScript in more detail.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
module app { // define all the members that a controller should implement. interface IHomeScope { counters: Array<any>; addCounters(updatedCounters); } class HomeController implements IHomeScope { static $inject: Array<string> = ['$', 'SignalRService', '$scope']; constructor(private $: JQueryStatic, signalRService: SignalRService, private $scope: ng.IScope) { this.counters = []; $scope.$parent.$on('newCounters', (e, updatedCounters) => { this.addCounters(updatedCounters); }); } addCounters(updatedCounters: any) { // this uses jQuery's wrapper service for loop. You may use angular/underscore/lodash loop function. $.each(updatedCounters, (index, updatedCounter) => { var entry: ChartEntry = null; $.each(this.counters, (index, counter) => { if (counter.name === updatedCounter.name) { entry = counter; } }); if (!entry) { entry = new ChartEntry(updatedCounter.name); this.counters.push(entry); this.$scope.$apply(); // so that canvas is rendered in HTML entry.start(); } entry.addValue(updatedCounter.value); }); } counters: any[]; } angular.module('app').controller('HomeController', HomeController); } |
It is quite a simple class with counters
member as an array that will hold all the counter values. As discussed above, the friendly name of every counter is used to identify whether the counter already exists or not. If it doesn’t exist then a new chart-entry is created and added to the counters array. And eventually, the value is added to the entry irrespective of whether entry is new or old. This also asks angular to inject SignalRService
that does all the work to use signalR properly.
The code that uses $on
event hook will be explained later in the post.
ChartEntry.ts
file is created to define a ChartEntry
class internally uses SmoothieChart which defines a chart for a counter. SmoothieChart has a timeSeries
which it uses to track a value at particular time. Instead of matching server & client time, we just use client-side time to append a counter’s value. The implementation of this type looks like this below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
/// <reference path="../scripts/typings/smoothie/smoothie.d.ts" /> module app { export class ChartEntry { name: string; chart: any; timeSeries: any; constructor(name: string) { this.name = name; // SmoothieChart usage here can also be wrapped inside a angular directive this.chart = new SmoothieChart({ millisPerPixel: 50, labels: { fontSize: 15 } }); this.timeSeries = new TimeSeries(); this.chart.addTimeSeries(this.timeSeries, { lineWidth: 3, strokeStyle: "#00ff00" }); console.log(this.chart); } addValue(value) { this.timeSeries.append(new Date().getTime(), value);; } start() { var canvas: HTMLCanvasElement = document.getElementById(this.name) as HTMLCanvasElement; this.chart.streamTo(canvas); } } } |
The important bit is still remaining to glue SignalR work done on server with client-side setup. Add signal.service.ts
file that looks like snippet below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
module app { export class SignalRService { static $inject: Array<string> = ['$', '$rootScope']; constructor(private $: JQueryStatic, $rootScope: ng.IRootScopeService) { // creates a new hub connection var connection = $.hubConnection("/signalr", { useDefaultPath: false }); // enabled logging to see in browser dev tools what SignalR is doing behind the scenes connection.logging = true; // create a proxy this.proxy = connection.createHubProxy('perfHub'); this.proxy.connection.logging = true; // start connection connection.start(); // publish an event when server pushes a newCounters message for client this.proxy.on('newCounters', results => { $rootScope.$emit('newCounters', results); }); } proxy: SignalR.Hub.Proxy; } angular.module('app').service('SignalRService', SignalRService); } |
This service is responsible to connect to /signalr
route to make a hub connection and a hub proxy named perfHub
. The logging is also enabled at connection and proxy level. The connection is then started.
The event method is used via $emit
method to publish an event whenever server pushes a newCounters
message for client. The listeners can then listen to this event and handle it appropriately. In this app, home controller is listening for this event which is defined in its constructor. Internally it calls the addCounters
method to update the chart with new value. Using this event concept is useful as this makes service and controller loosely coupled to react for any messages sent from the server.
If you have followed all the instructions in this post, you will now have a working real-time web application which will look like this:

Real-time web app showing Process, Paging and Disk usage of a server.
This will work well on IIS Express on your local machine but make sure to enable Web Sockets for IIS if you are using IIS 8 and above as by default it is turned off.
This sums up this post to write real-time web app using SignalR, TypeScript, AngularJS, HTML5. If you face any issues, let me know and I’ll try to help you. This just scratches the surface and there are many other great real-time usages that can be achieved using SignalR.