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.

Article by: Eric Ditter

Subscribe to our Newsletter

Stay informed on the latest technology news and trends

Relevant Insights

7 Key Guidelines to Accelerate Your Zero Trust Journey

[The following is an excerpt from a 57-minute long webinar on Zero Trust you can watch on-demand here.] Zero Trust...

5 New Updates to Microsoft Teams | May 2022

This month we noticed several new Microsoft Teams enhancements designed to ensure a better user experience. In this update, we...

Prepare Your Company for the Future with Network Automation

Throughout my many years working in the IT space, I have planned and executed more network installations and upgrades than...