Alexander Smith
Alexander Smith

Reputation: 379

SignalR and AngularJS

I use SignalR for my app, and it works with simple javascript,but now I'm trying to rewrite it with angular-wrapper https://github.com/JustMaier/angular-signalr-hub, but I run into the error while trying to connect to my hub:

Error: SignalR: Connection must be started before data can be sent. Call .start() before .send()
at Object.signalR.fn.signalR.send (http://localhost:47124/Scripts/jquery.signalR-2.1.1.js:789:23)
at Object.hubProxy.fn.hubProxy.invoke (http://localhost:47124/Scripts/jquery.signalR-2.1.1.js:2609:24)
at Hub.invoke (http://localhost:47124/Scripts/angular-signalr-hub.js:30:28)
at Hub.(anonymous function) [as loadRecentPhotos] (http://localhost:47124/Scripts/angular-signalr-hub.js:49:24)
at Object.PhotoMarkers.load (http://localhost:47124/Scripts/AngApp.js:47:17)
at http://localhost:47124/Scripts/angular.js:10836:21
at Scope.$eval (http://localhost:47124/Scripts/angular.js:12673:28)
at pre (http://localhost:47124/Scripts/angular.js:19943:15)
at nodeLinkFn (http://localhost:47124/Scripts/angular.js:6684:13)
at compositeLinkFn (http://localhost:47124/Scripts/angular.js:6098:13) <div class="modal-body" ng-init="markers.load()"> 

Here is my code:

angular.module('FlickrMaps', ['SignalR'])
.factory('PhotoMarkers', ['$rootScope', 'Hub', function ($rootScope, Hub) {
    var PhotoMarkers = this;

    //Hub setup
    var hub = new Hub('photos', {
        listeners: {
            'addTotalPhotosCount': function (total) {
                PhotoMarkers.totalCount = total;
                $rootScope.$apply();
            },

            'initPhotoMarker': function (photo) {
                var photolocation = new window.google.maps.LatLng(photo.Latitude, photo.Longitude);
                PhotoMarkers.all.push(new window.google.maps.Marker({
                    position: photolocation,
                    title: photo.Title,
                    photoThumb: photo.PhotoThumbScr,
                    photoOriginal: photo.PhotoOriginalScr
                }));
                $rootScope.$apply();
            },

            'photosProcessed': function () {
                PhotoMarkers.processedPhotosCount++;
                $rootScope.$apply();
            },
        },
        methods: ['loadRecentPhotos'],
        errorHandler: function (error) {
            console.error(error);
        }
    });

    //Variables
    PhotoMarkers.all = [];
    PhotoMarkers.processedPhotosCount = 0;
    PhotoMarkers.totalCount = 0;

    //Methods
    PhotoMarkers.load = function () {
        $('#myModal').modal({
            backdrop: 'static',
            keyboard: false
        });
        hub.loadRecentPhotos();
    };

    return PhotoMarkers;
}])
.controller('MapController', ['$scope', 'PhotoMarkers', function ($scope, PhotoMarkers) {
$scope.markers = PhotoMarkers;
}])

Does anyone acquainted with that?

Upvotes: 4

Views: 4222

Answers (6)

Dunc
Dunc

Reputation: 18922

What helped me when wrapping SignalR for AngularJS use was this:

** Convert all SignalR (jQuery) promises to Angular $q promises. **

The benefits:

  1. All callers have a consistent promise interface (e.g. finally() rather than always())

  2. Promise is rejected on error (no need for try...catch), e.g. if connection is not started, as per your question

  3. Angular's $digest cycle is fired

With that in mind, you could change your Hub.invoke method to this:

Hub.invoke = function(method, args) {
    try {
       var jQueryPromise = Hub.proxy.invoke.apply(Hub.proxy, arguments);

        return $q.when(jQueryPromise);    // <- return as $q promise

    } catch(e) {
        return $q.reject(e);              // <- exception to $q.reject
    }

and Hub.connect to

Hub.connect = function (queryParams) {
    ...
    return $q.when(Hub.connection.start(startOptions));  // <- return $q promise
};

Upvotes: 0

ApiFox
ApiFox

Reputation: 123

make sure you install and reference jquery.signalR-2.1.2.js file

Upvotes: 0

Louis Lewis
Louis Lewis

Reputation: 1298

I have a basic working example of this on Github. Hope it helps. While it does use authentication, if this is not important for you. You can simply remove the authentication part.

https://github.com/louislewis2/AngularJSAuthentication

Regards

Louis

Upvotes: 1

Jacques Snyman
Jacques Snyman

Reputation: 4290

This is discussed [here]

vmlf01's comment is:

The hub created has a ".promise" property that corresponds to the return value from ".start()". I initialize my connection using a promise and resolving it in the done() callback when the connection is established, like so:

    function initialize() {
        var deferred = $q.defer();

        foldersHubConnection = new Hub('Folders', {
            listeners: folderHubListeners(),
            methods: folderServerCalls(),
            rootPath: config.eventsServerEndpoint
        });

        foldersHubConnection.promise.done(function () {
            deferred.resolve(foldersHubClient);
        });

        foldersHubConnection.promise.fail(function (error) {
            deferred.reject(new Error("Error connecting to Events Server: " + error));
        });

        return deferred.promise;
    }

Upvotes: 0

Khanh TO
Khanh TO

Reputation: 48972

You must be running into asynch issues. When you invoke method, the connection is not open yet.

Try fixing your signalr-hub.js, I think we should use different promises for stop and start.

    Hub.disconnect = function () {
        Hub.stopPromise = Hub.connection.stop();
    };
    Hub.connect = function () {
        Hub.startPromise = Hub.connection.start();
    };

And:

angular.forEach(options.methods, function (method) {
        Hub[method] = function () {
            var args = $.makeArray(arguments);
            args.unshift(method);
            return Hub.startPromise.then(function(){
                //invoke method only after startPromise is resolved
                 return Hub.invoke.apply(Hub, args);
            }
        };
});

Or:

angular.forEach(options.methods, function (method) {
        Hub[method] = function () {
            var args = $.makeArray(arguments);
            args.unshift(method);

            var def = $q.def();
            Hub.startPromise.then(function(){
                    //invoke method only after startPromise is resolved
                  Hub.invoke.apply(Hub, args).done(function(){
                         def.resolve();
                  });
            }

            return def.promise;
        };
 });

Don't forget to inject $q into your Hub. This returns an angular promise instead of a jQuery promise. Therefore, angular is aware of the event, we don't need to use $rootScope.$apply() in your callback.

And:

//Adding additional property of promise allows to access it in rest of the application.
   Hub.startPromise = Hub.connection.start();

Upvotes: 3

Sabacc
Sabacc

Reputation: 789

According to the source code of the SignalR-Hub project, the connection should be started when creating a new hub. Have you checked your browser console for possible connection errors?

The library also provides access to the start() promise. Attach yourself to this promise to check wether the hub has successfully ben connected to your server. Example:

hub.promise
    .done(function(){ console.log('connection established'); })
    .fail(function(){ console.log('connection error'); });

the promise object is the access to the promise of the signalR start method.

Upvotes: 0

Related Questions