Skip to content

Instantly share code, notes, and snippets.

Created March 5, 2015 20:05
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save anonymous/b552c7fafa77427e6d06 to your computer and use it in GitHub Desktop.
Save anonymous/b552c7fafa77427e6d06 to your computer and use it in GitHub Desktop.
angular SignalTowerService

angular SignalTowerService

Adds the js-signals implementation to AngularJS. There is no good way for AngularJS services to send or receive events. The 'angular way' for events to be sent uses $rootScope, but angular services should never know about any scope. Enter SignalTowerService and it's publish/subscribe model of Signals ala js-signals

A Pen by Keldon Rush on CodePen.

License.

angular
.module( 'services.SignalTowerService', [] )
.service( 'SignalTowerService', function SignalTowerService() {
var CN = "SignalTowerService";
var self = this;
self.signals = {};
// START : exported service methods
function createSignal( signalName ) {
var newSignal;
if( self.signals.hasOwnProperty( signalName ) && self.signals[ signalName ] !== null ) {
newSignal = self.signals[ signalName ];
} else {
newSignal = new Signal();
self.signals[ signalName ] = newSignal;
}
return newSignal;
}
function destroySignal( signalName ) {
if( self.signals.hasOwnProperty( signalName ) && self.signals[ signalName ] !== null ) {
self.signals[ signalName ] .dispose();
self.signals[ signalName ] = null;
}
}
function dispatchSignal( signalName, payload ) {
if( self.signals.hasOwnProperty( signalName ) && self.signals[ signalName ] !== null ) {
self.signals[ signalName ].dispatch( payload );
}
}
function subscribeToSignal( signalName, listener, listenerContext, priority ) {
if( self.signals.hasOwnProperty( signalName ) && self.signals[ signalName ] !== null ) {
self.signals[ signalName ].add( listener, listenerContext, priority );
} else {
var newSignal = createSignal( signalName );
newSignal.add( listener, listenerContext, priority );
}
}
function subscribeToSignalOnce( signalName, listener, listenerContext, priority ) {
if( self.signals.hasOwnProperty( signalName ) && self.signals[ signalName ] !== null ) {
self.signals[ signalName ].addOnce( listener, listenerContext, priority );
} else {
var newSignal = createSignal( signalName );
newSignal.addOnce( listener, listenerContext, priority );
}
}
function unSubscribeToSignal( signalName, listener, context ) {
if( self.signals.hasOwnProperty( signalName ) && self.signals[ signalName ] !== null ) {
self.signals[ signalName ].remove( listener, context );
}
}
// END : exported service methods
// START : js-signals implementation
/*
* Below here is the js-signals implementation v1.0.0 (2012/11/29) from :
* https://github.com/millermedeiros/js-signals/
*
* The MIT License (MIT) is granted for the code between
* http://opensource.org/licenses/mit-license.php
* // START : js-signals implementation
* and
* // END : js-signals implementation
*
*/
function SignalBinding( signal, listener, isOnce, listenerContext, priority ) {
this._listener = listener;
this._isOnce = isOnce;
this.context = listenerContext;
this._signal = signal;
this._priority = priority || 0;
}
SignalBinding.prototype = {
active : true,
params : null,
execute : function( paramsArr ) {
var handlerReturn, params;
if( this.active && !!this._listener ) {
params = this.params ? this.params.concat( paramsArr ) : paramsArr;
handlerReturn = this._listener.apply( this.context, params );
if( this._isOnce ) {
this.detach();
}
}
return handlerReturn;
},
detach : function() {
return this.isBound() ? this._signal.remove( this._listener, this.context ) : null;
},
isBound : function() {
return (!!this._signal && !!this._listener);
},
isOnce : function() {
return this._isOnce;
},
getListener : function() {
return this._listener;
},
getSignal : function() {
return this._signal;
},
_destroy : function() {
delete this._signal;
delete this._listener;
delete this.context;
},
toString : function() {
return '[SignalBinding isOnce:' + this._isOnce + ', isBound:' + this.isBound() + ', active:' + this.active + ']';
}
};
function validateListener( listener, fnName ) {
if( typeof listener !== 'function' ) {
throw new Error( 'listener is a required param of {fn}() and should be a Function.'.replace( '{fn}',
fnName ) );
}
}
function Signal() {
this._bindings = [];
this._prevParams = null;
var self = this;
this.dispatch = function() {
Signal.prototype.dispatch.apply( self, arguments );
};
}
Signal.prototype = {
VERSION : '1.0.0',
memorize : false,
_shouldPropagate : true,
active : true,
_registerListener : function( listener, isOnce, listenerContext, priority ) {
var prevIndex = this._indexOfListener( listener, listenerContext ),
binding;
if( prevIndex !== -1 ) {
binding = this._bindings[ prevIndex ];
if( binding.isOnce() !== isOnce ) {
throw new Error( 'You cannot add' + (isOnce ? '' : 'Once') + '() then add' + (!isOnce ? '' : 'Once') + '() the same listener without removing the relationship first.' );
}
}
else {
binding = new SignalBinding( this,
listener,
isOnce,
listenerContext,
priority );
this._addBinding( binding );
}
if( this.memorize && this._prevParams ) {
binding.execute( this._prevParams );
}
return binding;
},
_addBinding : function( binding ) {
var n = this._bindings.length;
do {
--n;
} while( this._bindings[ n ] && binding._priority <= this._bindings[ n ]._priority );
this._bindings.splice( n + 1, 0, binding );
},
_indexOfListener : function( listener, context ) {
var n = this._bindings.length,
cur;
while( n-- ) {
cur = this._bindings[ n ];
if( cur._listener === listener && cur.context === context ) {
return n;
}
}
return -1;
},
has : function( listener, context ) {
return this._indexOfListener( listener, context ) !== -1;
},
add : function( listener, listenerContext, priority ) {
validateListener( listener, 'add' );
return this._registerListener( listener, false, listenerContext, priority );
},
addOnce : function( listener, listenerContext, priority ) {
validateListener( listener, 'addOnce' );
return this._registerListener( listener, true, listenerContext, priority );
},
remove : function( listener, context ) {
validateListener( listener, 'remove' );
var i = this._indexOfListener( listener, context );
if( i !== -1 ) {
this._bindings[ i ]._destroy();
this._bindings.splice( i, 1 );
}
return listener;
},
removeAll : function() {
var n = this._bindings.length;
while( n-- ) {
this._bindings[ n ]._destroy();
}
this._bindings.length = 0;
},
getNumListeners : function() {
return this._bindings.length;
},
halt : function() {
this._shouldPropagate = false;
},
dispatch : function( params ) {
if( !this.active ) {
return;
}
var paramsArr = Array.prototype.slice.call( arguments ),
n = this._bindings.length,
bindings;
if( this.memorize ) {
this._prevParams = paramsArr;
}
if( !n ) {
return;
}
bindings = this._bindings.slice();
this._shouldPropagate = true;
do {
n--;
} while( bindings[ n ] && this._shouldPropagate && bindings[ n ].execute( paramsArr ) !== false );
},
forget : function() {
this._prevParams = null;
},
dispose : function() {
this.removeAll();
delete this._bindings;
delete this._prevParams;
},
toString : function() {
return '[Signal active:' + this.active + ' numListeners:' + this.getNumListeners() + ']';
}
};
// END : js-signals implementation
var signalTowerService = {
createSignal : createSignal,
destroySignal : destroySignal,
dispatchSignal : dispatchSignal,
subscribeToSignal : subscribeToSignal,
subscribeToSignalOnce : subscribeToSignalOnce,
unSubscribeToSignal : unSubscribeToSignal
};
return signalTowerService;
} );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment