Skip to content

Instantly share code, notes, and snippets.

@joelhooks
Last active January 2, 2016 17:49
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save joelhooks/bc5a0065ee661d2588c9 to your computer and use it in GitHub Desktop.
Save joelhooks/bc5a0065ee661d2588c9 to your computer and use it in GitHub Desktop.
This is a global event bus for AngularJS applications. It abstracts `$rootScope` and uses it as the bus. With the `DispatchingController` mixin, controllers and add events easily and not be concerned with memory-leaks (or worse, catastrophic event handling by "dead" controllers). It also adds in some helper methods for "off-scope" services that …
angular.module('event-dispatcher', [])
/**
* This factory provides a constructor to mixin with an
* AngularJS controller constructor. It provides some
* syntax sugar for making memory-leak safe eventing.
*
* We define the constructor as a factory so that we can
* inject the constructor. This is useful for testing so
* we aren't hard-instantiating a namespaced global function
* and can substitute it when needed.
*/
.factory('DispatchingController', function ($rootScope) {
function DispatchingController($scope) {
var delisteners = [];
if (!$scope) {
throw new Error("A DispatchingController must have $scope to function. It wasn't found.")
}
this.dispatch = $rootScope.$emit.bind($rootScope);
this.listen = function () {
var args = Array.prototype.slice.call(arguments),
deListenFunc = $rootScope.$on.apply($rootScope, args);
delisteners.push(deListenFunc);
return deListenFunc;
};
// this is the "why" of this mixin. We want to be able to
// listen for events on the rootScope bus, but we also
// want to make sure we don't have memory leaks.
// Instead of managing all of that in the controller, we
// can use this mixin and get very clean/safe results.
$scope.$on('$destroy', function () {
delisteners.forEach(function (deListenFunc) {
deListenFunc.call();
})
})
}
return DispatchingController;
})
/**
* This factory provides a `dispatch` function as
* an injectable. The dispatch function is a wrapper
* around $rootScope.$emit, but allows us to inject
* just the single piece of functionality we want
* into ANY service/factory/etc and facilitate event
* dispatching.
*
* We want to discourage services (models) from LISTENING
* for events, but they need to dispatch events to notify
* other actors when changes occur.
*/
.factory('dispatch', function (Dispatcher) {
var dispatcher = new Dispatcher();
return dispatcher.dispatch;
})
/**
* This factory creates a Dispatcher constructor. This can
* be used as a method for mixing in dispatching capabilities.
*
* Generally we'll just use the dispatch function above.
*/
.factory('Dispatcher', function($rootScope) {
function Dispatcher() {}
Dispatcher.prototype.dispatch = function () {
var args = Array.prototype.slice.call(arguments);
$rootScope.$emit.apply($rootScope, args);
};
return Dispatcher;
})
;
// this might be sinful, but it is pretty and I like it ;)
Object.defineProperty(Object.prototype, 'mixin', {
value: function (receiver, giver) {
for (var i in giver) {
if (!this.hasOwnProperty(i)) {
Object.defineProperty(receiver, i, {
value: giver[i],
enumerable: true
});
}
}
},
enumerable: false
});
/**
* This is how you'd use the dispatcher
*/
.controller('myController', function ($scope, DispatchingController) {
var time = new Date();
// `this` scope can be an issue here. It might be
// required to assign `var myController = this`
// in some cases.
//
// this could be `angular.extend` vs the Object.mixin ;)
this.mixin(this, new DispatchingController($scope));
this.listen('hi', function (event, msg) {
console.log('event handled:', time, msg, event);
});
this.dispatch('hi', 'from controller')
})
.service('myService', function(dispatch) {
dispatch('hi', 'from service');
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment