Using SignalR Base Classes in Angular

By: Eric Ditter | March 6, 2018

On one of my previous projects I used SignalR extensively in an Angular 1.6 application, and I was using the angular-signalr-hub library to integrate it into the application. It worked very well, but I am moving to the next version of Angular so I wanted to find a way to do it without having to use another library. I was hoping to have a more object-oriented approach to it, which ultimately led me to just write something myself.

With Typescript, you can use base classes very easily so I ended up coming up with the following code. Overall, I really like how it came out. Everything that comes or goes between my app and the server passes through this which can be very powerful. In the past, I have done some date parsing or conversions from an array like value to a regular array, and my calling code just worked without knowing the difference (even IE 8). That, as well as removing all of the boilerplate code from the classes, has made this piece of code very useful.


/// <reference types="signalr" />
import { NgZone, Injector } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Observable';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
export enum ConnectionState {
  Connecting = 0,
  Connected = 1,
  Reconnecting = 2,
  Disconnected = 4
export abstract class SignalrBase {
  // static so I can have a shared connection
  private static connections: { [url: string]: SignalR.Hub.Connection } = {};
  [propertyName: string]: any;
  public connectionState$ = new BehaviorSubject<ConnectionState>(ConnectionState.Disconnected);
  public error$ = new Subject<SignalR.ConnectionError>();
  private callbackQueue: (() => void)[] = [];
  private connection: SignalR.Hub.Connection;
  private proxy: SignalR.Hub.Proxy;
  private ngZone: NgZone;
  protected constructor(
    private hubName: string,
    private listenerNames: string[],
    private methodNames: string[],
    private injector: Injector,
  ) {
    this.ngZone = this.injector.get(NgZone);
    this.connection = SignalrBase.InitConnection('/MySite/SignalR');
    this.proxy = this.connection.createHubProxy(hubName);
    // Define handlers for the connection state events
    this.connection.stateChanged(state => {
      switch (state.newState) {
        case ConnectionState.Connected:
          while (this.callbackQueue.length > 0) {
            const callback = this.callbackQueue.pop();
            if (callback instanceof Function) {
        case ConnectionState.Disconnected:
          const timeoutKey = setInterval(async (intervalState: { attempt: number }) => {
            if (intervalState.attempt > 30) {
              console.error(`[SignalR][${this.hubName}] Clearing Timeout for disconnect`);
            try {
              await this.connection.start();
            } catch (err) {
              console.error(`[SignalR][${this.hubName}]`, err);
          }, 10000, { attemptNum: 0 }); // Try to restart connection after 10 seconds
    // Define handlers for any errors
    this.connection.error(error => {
      console.error(`[SignalR][${this.hubName}]`, error);
    // Build up the class methods
    this.listenerNames.forEach(listenerName => this.buildListener(listenerName));
    this.methodNames.forEach(methodName => this.buildMethod(methodName));
  private static InitConnection(url: string) {
    // Check for the Signalr dependencies
    const jq = (window as any).jQuery as SignalR;
    if (typeof jq === 'undefined') {
      throw new Error(`The variable "jQuery" is not defined...please check that jQuery has been loaded properly`);
    } else if (!(jq.hubConnection instanceof Function)) {
      throw new Error(`The 'jQuery.hubConnection()' function is not defined...please check that SignalR has been loaded properly`);
    // we do this so the connection can be shared per URL
    if (typeof this.connections[url] === 'undefined') {
      this.connections[url] = jq.hubConnection(url);
        .done(d => {
        .fail(error => {
    return this.connections[url];
  private buildMethod(methodName: string) {
    // add the method to the object
    this[methodName] = (...args: any[]) => {
      const results = new Subject<any>();
      const invokeCall = () => {
        this.proxy.invoke(methodName, ...args)
          .done((results: any) => {
          .fail((error: any) => {
   => results.error(error));
          .always(() => {
      if (this.connectionState$.getValue() !== ConnectionState.Connected) {
        // in the case that we aren't connected to the hub
        // queue up the call which will run in the this.connection.stateChanged callback
      } else {
        // otherwise just make the call
      return results.asObservable();
  private buildListener(listenerName: string) {
    // converts the name from 'message' to 'onMessage'
    const methodName = 'on' + listenerName.charAt(0).toUpperCase() + listenerName.slice(1);
    const results = new Subject<any>();
    this.proxy.on(listenerName, resultData => { =>;
    // add the listener to the object
    this[methodName] = results.asObservable();

This is my implementing class, which is pretty barebones. The only thing I wasn’t able to figure out was getting NgZone injected directly into the base class. Instead, I pass the Injector type in from the implementing class which will be used to get anything the base class might need. It isn’t perfect, but it is pretty minor to have one extra parameter.

If you aren’t using Angular, then you can simply remove all references to NgZone and everything should work fine. NgZone is only used to trigger the UI to update. Also, because we are using RXJS Observables we can easily intercept anything that comes through at a class level and pass that along, which is what onBase64Message does.


import { Injectable, Injector } from '@angular/core';
import { SignalrBase } from './SignalrBase';
export class DataService extends SignalrBase {
  // Listeners
  public onMessage: Observable<{ username: string, text: string }>;
  public onBase64Message: () => Observable<string>;
  // Methods
  public getData: () => Observable<string[]>;
  constructor(injector: Injector) {
        // this will be converted to onMessage in the base class
      ], injector);
      // a message from the server like 'SGVsbG8gV29ybGQ=' will become 'Hello World'
      this.onBase64Message = string) => atob(encodedString))

And here is a simple example of how it is used in a component:


import { DataService } from '../';
import { Component } from '@angular/core';
  selector: 'my-messages',
  template: 'messages.component.html',
  styleUrls: ['messages.component.scss'],
export class MessagesComponent {
  public messages: string[] = [];
    private dataService: DataService,
  ) {
    this.dataService.getData.subscribe(messages => this.messages = messages);
    this.dataService.onMessage.subscribe(info => this.messages.push(`${info.username}: ${info.text}`));
    this.dataService.onBase64Message.subscribe(message => this.messages.push(message));

The Power of JavaScript

The project this came from has about a half-dozen hubs. This relatively simple piece of code has definitely come in handy by reducing the need for copy and pasted code. If you aren’t using a library that is built around RXJS like Angular is, you can easily change this to use Promises and every implementing class will simply work. This kind of dynamic coding can seem like magic at times but it definitely shows the power of some JavaScript.

Learn more about our software development capabilities.

Eric is an experienced Software Engineer with a demonstrated history of working in the information technology and services industry. He is skilled in JavaScript, .NET, ASP.NET MVC, SQL Server, and Python.

Subscribe to our Newsletter

Stay informed on the latest technology news and trends

Relevant Insights

How to Turn Your Software Licensing into a Strategic Asset

Most people groan when they hear the words “software licensing”. Licensing can be complicated, and usually businesses will continue with...
Read More

How to Manage Your Hybrid Workforce with Compliance

The pandemic accelerated a real change in the way people do their work. Before, employees worked in a physical office...
Read More

Windows Server 2012 End-of-Life Risks, Options, and Next Steps

In October 2023, Windows Server 2012/2012 R2 will reach the end of extended support – leaving your infrastructure and applications...
Read More