AngularJS and SignalR

The goal of this post is to connect and use SignalR from AngularJS. All the code is available in GitHub.

All code is available here: GitHub Link

The application used in this post allows to add tasks to a collaborative list of tasks to do. The application displays a list with all the tasks pending to do and also allows to remove them from the list. SignalR will push the notification “newTask” and “taskDone” to all connected users.

Feel free to get the code from github and run the application in more than one place (different tabs in your explorer) to see how SignalR synchronizes all of them.

The simplicity of the application allows to focus on the goal of this post: SignalR and AngularJS.

This is a screenshot of the running application.

Screenshot of the signalR and angularJS app.

SignalR Hub

The first thing that we need is to configure the OWIN startup, we need this class in our project:

[assembly: OwinStartup(typeof(Startup))]
namespace AngularJS_SignalR
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            // SignalR
            app.MapSignalR();
        }
    }
}

The signalR hub type is “INotesCallbacks”, this interface defines the callbacks that the hub can make to the connected clients.

    // Client callbacks
    public interface INotesCallbacks
    {
        // Notify note added
        Task BroadcastNewNote(Note newNote);
        // Notify note removed
        Task BroadcastRemoveNote(int noteId);
    }

The hub also implement the interface “INotesCalls” this interfaces defines the hub methods that clients can call.

    // Client calls
    public interface INotesCalls
    {
        // Add note
        Task AddNote(string note);
        // Get all notes
        IEnumerable<Note> GetAllNotes();
        // Remove note
        Task RemoveNote(int roomId);
    }

This is the implementation of the hub:

    [HubName("notesHub")]
    public class NotesHub : Hub<INotesCallbacks>, INotesCalls
    {
        public async Task AddNote(string note)
        {
            Note newNote = NotesService.Add(note);

            // All connected clients will receive this call
            await Clients.All.BroadcastNewNote(newNote);
        }

        public IEnumerable<Note> GetAllNotes()
        {
            return NotesService.GetAll();
        }

        public async Task RemoveNote(int noteId)
        {
            if (NotesService.Remove(noteId))
            {
                // All connected clients will receive this call
                await Clients.All.BroadcastRemoveNote(noteId);
            }
        }
    }

Angular

The application has a controller with all the bindings that the view can do and a service with all the logic needed to:

  • stablish a connection with SignalR
  • register callbacks functions
  • make hub calls

You can see the view here, the controller here and the service here.

Stablish the connection with SignalR

The angular service implement a method “initialize” that makes the connection to SignalR and broadcast an event when the connection was established.

        var _hubConnection = $.hubConnection();
        var _notesHubProxy = undefined;

        //// Service methods implementation
        function _initialize() {

            // Hub Proxy (allows to make calls and register callbacks handlers)
            _notesHubProxy = _hubConnection.createHubProxy(notesSignalR.hubName);

            // signalR callbacks handlers
            _notesHubProxy.on(notesSignalR.onNewNote, broadcastNewNote);
            _notesHubProxy.on(notesSignalR.onRemoveNote, broadcastRemoveNote);

            // connect
            _hubConnection.start()
                .done(connectedToSignalR)
                .fail(function () { console.error('Error connecting to signalR'); });
        }

        function connectedToSignalR() {
            console.debug('connected to signalR, connection ID =' + _hubConnection.id);
            $rootScope.$broadcast(signalR.onConnected, { connectionId: _hubConnection.id });
        }

To avoid magic strings on the javascript code we define this constants:

function signalR() {
}

// connected event
signalR.onConnected = "signalRConnected";

function notesSignalR() {
}

// hub
notesSignalR.hubName = "notesHub";

// client calls
notesSignalR.addNote = "addNote";
notesSignalR.removeNote = "removeNote";
notesSignalR.getAllNotes = "getAllNotes";

// client callbacks
notesSignalR.onNewNote = "broadcastNewNote";
notesSignalR.onRemoveNote = "broadcastRemoveNote";

Three different scenarios to communicate with SignalR from AngularJS will be described:

  • Register functions to be run when SignalR makes a callback to the client.
  • Call SignalR from AngularJS. Functions without a return parameter.
  • Call SignalR from AngularJS. Function returning values to the client.

Register functions to SignalR callbacks

The chosen implementation will register a function that broadcasts the callback received from SignalR. All the controllers interested in this callback shall be register to this AngularJS broadcast, controllers don’t even know that trigger was SignalR.

// signalR callbacks handlers
_notesHubProxy.on(notesSignalR.onNewNote, broadcastNewNote);
_notesHubProxy.on(notesSignalR.onRemoveNote, broadcastRemoveNote);

function broadcastNewNote(note) {
    console.debug(notesSignalR.onNewNote + " " + note.text);
    $rootScope.$broadcast(notesSignalR.onNewNote, { note: note });
}

function broadcastRemoveNote(noteId) {
    console.debug(notesSignalR.onRemoveNote + " " + noteId);
    $rootScope.$broadcast(notesSignalR.onRemoveNote, { noteId: noteId });
}

The controller register this functions when the previous events are broadcast:

 //// PRIVATE Functions - Public Methods Implementation	
function _activate() {
    notesService.initialize();

    $scope.$on(signalR.onConnected, function (event, args) {
        connectedToSignalR(args.connectionId);
    });

    $scope.$on(notesSignalR.onNewNote, function (event, args) {
        $scope.$apply(function() {
            vm.notes.push(args.note);
        });
    });

    $scope.$on(notesSignalR.onRemoveNote, function (event, args) {
        $scope.$apply(function () {
            removeNote(args.noteId);
        });
    });
}

function _addNote() {
    notesService.addNote(vm.newNote);
    vm.newNote = "";
}

function _removeNote(id) {
    notesService.removeNote(id);
    removeNote(id);
}

Call SignalR from AngularJS

To call SignalR from angular we only need a simple invoke in our service:

function _addNote(note) {
    _notesHubProxy.invoke(notesSignalR.addNote, note);
}

function _removeNote(noteId) {
    _notesHubProxy.invoke(notesSignalR.removeNote, noteId);
}

Call SignalR from AngularJS with return values. Promises.

Things are a little bit more complicated if we expect a return value from the method in SignalR because the call is asynchronous. An example is the “GetAllNotes” that the hub defines.

The method in our service will return a promise, from the controller we need to call the method and register the function that shall be executed after the asynchronous call is finished, i.e. the promise was resolved.

The method in our service:

function _getAllNotesAsync() {

    var deferred = $q.defer();

    _notesHubProxy.invoke(notesSignalR.getAllNotes)
       .done(function (notes) {
           deferred.resolve(notes);
       });

    return deferred.promise;
}

How to call this method from the controller:

// load all notes
notesService.getAllNotesAsync().then(function(notes) {
    vm.notes = notes;
});

Fell free to leave a comment!

6 thoughts on “AngularJS and SignalR

  1. Hi, I read your post and I like it. But, I have one question for how I can handle multiple requests to signalr? Let say during loading page I am calling two or more back end methods and I want to keep landing page until I don’t get all the data from the server and then remove landing page and show home page. Thank you in advance for your answer.

    Like

  2. Sorry, I send you code example so late. Here is what I am need:

    Here is my service:
    var menuItemshub = new Hub(‘menuItemsHub’, {
    rootPath: $rootScope.defaultRootPath,
    //server side methods
    methods: [‘getMenuItems’],
    queryParams: {
    ‘token’: $rootScope.loggedInUser.accessToken,
    },
    //handle connection error
    errorHandler: function (error) {
    console.error(error);
    }
    });

    var countrieshub = new Hub(‘countriesHub’, {
    rootPath: $rootScope.defaultRootPath,
    //server side methods
    methods: [‘getCountries’],
    queryParams: {
    ‘token’: $rootScope.loggedInUser.accessToken,
    },
    //handle connection error
    errorHandler: function (error) {
    console.error(error);
    }
    });

    var menuItemshubInitDone = function () {
    return menuItemshub.promise.done();
    };

    var countrieshubInitDone = function () {
    return countrieshub.promise.done();
    };

    var getMenuItems = function () {
    return menuItemshub.getMenuItems();
    };

    var getCountries = function () {
    return countrieshub.getCountries();
    };

    Here is my controller
    configurationService.menuItemshubInitDone().then(function () {
    configurationService.getMenuItems().then(function (response) {
    // Success
    $rootScope.menuItems = response.MenuItems;
    }, function (error) {
    });
    });

    configurationService.countrieshubInitDone().then(function () {
    configurationService.getCountries().then(function (response) {
    // Success
    $rootScope.countries = response.Countries;
    $rootScope.selectedAction = $rootScope.countries;
    $rootScope.setAction($rootScope.selectedAction[0]);

    }, function (error) {
    });
    });

    And I want to do something like:
    var all = $q.all([configurationService.getCountries(), configurationService.getMenuItems()]);
    all.then(function () {
    $rootScope.showLandingPage = true;
    });

    But, I get following error SignalR: Connection has not been fully initialized. Use .start().done() or .start().fail() to run logic after the connection has started.

    I am beginner with Angular so thank you in advance for your help.

    Like

Leave a Reply to Miroslav Cancel reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.