HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMinor

Angular wrapper for SignalR event aggregator

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
angularaggregatorwrapperforsignalrevent

Problem

I created a little wrapper for one of my JavaScript libraries to enable Angular functionality. Are there any pitfalls I should be wary of with my code?

angular.module("signalR.eventAggregator", [])
    .run([
        "$rootScope", function($rootScope) {
        function createScope(scope) {
            scope.$on('$destroy', function() {
                signalR.eventAggregator.unsubscribe(scope);
            });

            return {
                subscribe: function(type, handler, constraint) {
                    signalR.eventAggregator.subscribe(type, function(e) {
                        handler(e);
                        if (scope.$phase == null) {
                            scope.$digest();
                        }
                    }, scope, constraint);
                },
                publish: function(event) {
                    signalR.eventAggregator.publish(event);
                }
            }
        }

        $rootScope.eventAggregator = function() {
            return this.__eventAggregator = this.__eventAggregator || createScope(this);
        };
    }
]);


More info on the code itself here.

It's used like this:

$scope.eventAggregator().subscribe(MyApp.MyEvent, onEvent);


Other scenarios it supports are

Generic events, (all events are server events proxied to javascript through a dynamic Owin javascript)

$scope.eventAggregator().subscribe(MyApp.MyGenericEvent.of("System.String"), onEvent);


Constraints, in this case listen to all events with id 5

$scope.eventAggregator().subscribe(MyApp.MyEvent, onEvent, { id: 5 });


It wraps an event-driven library, and when an event is triggered, I use digest to update the view, and it's this part of the code that I wonder about.

handler(e); could return a promise that I currently ignore. I tested the ng-click directive and it also ignores any promise returned from the click handler. So at least it's consistent with the existing event handlers in Angular.

Solution

Hmm... interesting use of run + $rootScope, but I would avoid it primarily because of $rootScope. It's essentially the "global space" of an Angular app. The only legit use I see for it is attaching event listeners ($rootScope.$on), and for broadcasting events ($rootScope.$emit).

As an alternative, you could wrap your event relay in a factory.

-
As a factory, the dependency is explicit. The dependent knows it's there and Angular will throw if the dependency is missing on initialization, unlike $rootScope where it only throws when you use it.

-
Factories are singletons. You don't have to worry about checking and using the existing or create a new one.

-
You're not polluting the $rootScope nor depending on something that might not be there or have been overridden by something in a lower, enclosing scope. This is a headache to debug, especially without tools like Batarang or ngInspector.

Additionally, you might also want to put your event names in a constant. That way, you have a global lookup of event names, and you won't be hardcoding string literals all over the app.

Here's an example of how it would look like as a factory (using implicit dependency injection syntax for brevity).

angular.module('SignalrModule', []);
  .constant('SIGNALR_EVENTS', {
    FOO_EVENT: 'fooevent',
    BAR_EVENT: 'barevent',
  })
  .factory('SignalrFactory', function(){
    return {
      publish: function(){...},
      subscribe: function(){...},
      unsubscribe: function(){...},
    };
  });

angular.module('app', ['SignalrModule'])
  .controller('MyController', function(SignalrFactory, SIGNALR_EVENTS){
    SignalrFactory.subscribe(SIGNALR_EVENTS.FOO_EVENT, function(){
      // on foo event
    });
  });


Now I did mention that angular has a built-in pub-sub system. We can just hook on to it so your controllers will simply use regular events over $rootScope (we're not adding stuff to $rootScope, just making it a relay for events). Not sure of the following works, but the concept is there.

angular.module('SignalrModule', []);
  .constant('SIGNALR_EVENTS', {
    FOO_EVENT: 'fooevent',
    BAR_EVENT: 'barevent',
  })
  .run(function($rootScope, SIGNALR_EVENTS){

    // Iterate through our registry of events
    Object.keys(SIGNALR_EVENTS).forEach(function(eventName){

      // Relay angular events to signalr over $rootScope
      $rootScope.$on(SIGNALR_EVENTS[eventName], function(){
        signalR.eventAggregator.publish(SIGNALR_EVENTS[eventName]);
      });

      // Relay signalr events to angular over $rootScope
      signalR.eventAggregator.subscribe(SIGNALR_EVENTS[eventName], function(e) {
        $rootScope.$emit(SIGNALR_EVENTS[eventName], e);
      });
    });

  });

angular.module('app', ['SignalrModule'])
  .controller('MyController', function($rootScope, SIGNALR_EVENTS){

    // Using regular angular-ish emit and on

    $rootScope.$emit(SIGNALR_EVENTS.FOO_EVENT, { foo: 'data' });

    $rootScope.$on(SIGNALR_EVENTS.BAR_EVENT, function(){
      // bar event emitted somewhere
    });
  });

Code Snippets

angular.module('SignalrModule', []);
  .constant('SIGNALR_EVENTS', {
    FOO_EVENT: 'fooevent',
    BAR_EVENT: 'barevent',
  })
  .factory('SignalrFactory', function(){
    return {
      publish: function(){...},
      subscribe: function(){...},
      unsubscribe: function(){...},
    };
  });

angular.module('app', ['SignalrModule'])
  .controller('MyController', function(SignalrFactory, SIGNALR_EVENTS){
    SignalrFactory.subscribe(SIGNALR_EVENTS.FOO_EVENT, function(){
      // on foo event
    });
  });
angular.module('SignalrModule', []);
  .constant('SIGNALR_EVENTS', {
    FOO_EVENT: 'fooevent',
    BAR_EVENT: 'barevent',
  })
  .run(function($rootScope, SIGNALR_EVENTS){

    // Iterate through our registry of events
    Object.keys(SIGNALR_EVENTS).forEach(function(eventName){

      // Relay angular events to signalr over $rootScope
      $rootScope.$on(SIGNALR_EVENTS[eventName], function(){
        signalR.eventAggregator.publish(SIGNALR_EVENTS[eventName]);
      });

      // Relay signalr events to angular over $rootScope
      signalR.eventAggregator.subscribe(SIGNALR_EVENTS[eventName], function(e) {
        $rootScope.$emit(SIGNALR_EVENTS[eventName], e);
      });
    });

  });

angular.module('app', ['SignalrModule'])
  .controller('MyController', function($rootScope, SIGNALR_EVENTS){

    // Using regular angular-ish emit and on

    $rootScope.$emit(SIGNALR_EVENTS.FOO_EVENT, { foo: 'data' });

    $rootScope.$on(SIGNALR_EVENTS.BAR_EVENT, function(){
      // bar event emitted somewhere
    });
  });

Context

StackExchange Code Review Q#107360, answer score: 4

Revisions (0)

No revisions yet.