How to dynamically add hub to SignalR and have dif

2019-04-11 02:46发布

问题:

I'm attempting to build a Dashboard with widgets (built as directives). I'd like to at a later stage have the ability to dynamically add widgets, but I'd only like the active widgets (and hubs) to receive data, thus if a widget isn't active i don't want the hub to be registered.

Eg for the duration of the user using the app there will be a global signalR context, as well as page specific ones, which will be spawned/destroyed as needed.

This is my best try ATM... which isn't working

factory

   (function () {
    'use strict';

    angular.module('app').factory('hubFactory', ['permissionSvc', 'common', hubFactory]);

    function hubFactory(permissionSvc, common) {

        var connection = [];

        return {
            context: function (name) {

                if (!connection[name]) {
                    var conn = $.connection;
                    conn.hub.url = common.serviceUrl + '/signalr';
                    conn.hub.start();

                    conn.prototype.addHub = function (hubName, options) {

                        // defaults
                        var opts = {
                            authorize: true
                        };

                        angular.extend(opts, options);

                        if (opts.authorize) {
                            permissionSvc.createPrefilter();
                        }

                        var hub = conn[hubName];

                        var run = function () {
                            hub.server.initialise();
                        };

                        return {
                            hub: hub,
                            run: run
                        };
                    };

                    connection[name] = conn;
                }

                return connection[name]();
            }
        };
    }
})();

widget directive-controller

 controller: function ($scope) {               

               var context = hubFactory.context('dashboard');

               var instance = context.addHub('agreementApprovalsHub');

               instance.hub.client.getAllUnapprovedAgreements = function (data) {

                    $scope.data = data;
                };

               instance.run();
            } 

The following needs to be called in the done method of the start... but what if i want to, start up the connection on page load, and then append hubs as needed (or is my thinking wrong?)

There are various problems:

var run = function () { hub.server.initialise(); };

vs

var run = function () { conn.hub.start().done(function() { hub.server.initialise(); }); };

TBH, it feels like i'm butchering the code, and need to probably start from scratch at this stage.. I'm thoroughly confused on how to go about it all, and if its even possible.

回答1:

I think I have a more elegant solution.

Service

 app.factory("signalrFactory", function ($rootScope, $http, $timeout) {
    var factory = {};
    factory.connection = $.connection;

    var startDoneFunctions = [];
    var debounce;

    factory.start = function (done) {
        factory.connection.hub.stop();

        if (done) {
            if (startDoneFunctions.indexOf(done) == -1) {
                startDoneFunctions.push(done);
            }
        }
        if (debounce) $timeout.cancel(debounce);
        debounce = $timeout(function () {
            factory.connection.hub.start().done(function () {
                for (var x = 0; x < startDoneFunctions.length; x++) {
                    startDoneFunctions[x]();
                }
            });
        }, 100);
    };

    return factory;
});

Use

controller('customerSummary', function ($scope, signalrFactory) {
        $scope.customerHub = signalrFactory.connection.customerHub;
        $scope.customer;
        $scope.beingEditedText = "";



        $scope.customerHub.client.beingEdited = function (text) {
            $scope.beingEditedText = text;
        };
        $scope.customerHub.client.doneEditing = function () {
            $scope.beingEditedText = "";
        };

        $scope.customerHub.client.updateCustomer = function (customer) {
            $scope.customer = customer;
            $scope.$apply();
        };

        $scope.init = function (id) {
            signalrFactory.start(function () {
                $scope.customerHub.server.getCustomer(id);

            });
        };
    });

What this way allows is to add when needed and not having to add each hub at bootstrap. I am now able to have different done() methods from anywhere.



回答2:

Note

While the solution below worked for me at first. I opted to use the solution provided above, which worked quite well in the end.


Original answer

My initial understanding of signalR was slightly flawed. I ended up creating a provider for this as well so i can configure it upfront.

Config

app.config(['signalrSvcProvider', function (signalrSvcProvider) {
    signalrSvcProvider.start(['global','dashboard']);
}]);

Provider

(function () {
    'use strict';

    angular.module('app').provider('signalrSvc', signalrSvc);

    function signalrSvc() {

        var self = this;

        self.hubs = [];

        this.start = function (hubs) {

            var connection = $.hubConnection("someurl/signalr");

            // convert hubNames to actual hub proxies, and append default client communication functions
            $.each(hubs, function (idx, hub) {
                var proxy = connection.createHubProxy(hub);

                // a minimum of one client function needs to be registered to be able to complete the connection
                proxy.on('pingBack', function (data) { console.log(hub.hubName + ", connected: " + data); });
                proxy.on('receiveError', function (data) { console.log(hub.hubName + ", error: " + data); });

                self.hubs[idx] = proxy;
            });

            // add security token before attmpting connect
            $.ajaxPrefilter(function (options) {
                if (!options.beforeSend) {
                    options.beforeSend = function (xhr) {
                        var token = $.cookie(".cttid").replace(/"/g,'');
                        xhr.setRequestHeader('Authorization', 'Bearer ' + token);
                    };
                }
            });

            // connects and run initialise for each hub
            connection.start({ transport: 'longPolling' })
                .done(function () {
                    $.each(self.hubs, function (idx, hub) {
                        hub.invoke('initialise');
                    });
                });
        };

        this.$get = ['filterFilter', function (filterFilter) {
            return {
                hub: function (name) {
                    return filterFilter(self.hubs, { hubName: name }, true)[0];
                }
            };
        }];
    }

})();

Usage

var hub = signalrSvc.hub('dashboard');

hub.on('getAllUnapprovedAgreements', function (data) {
      //do something
});