Signals.ts
Signals implementation for TypeScript.
It's a simple pub-sub message bus architecture that's completely decoupled from any UI or other component.
Inspired by JS-Signals which was inspired by AS3-Signals.
Why another implementation
I used many implementations through the years, manly Miller Medeiros' one.
What I though was missing was:
- TypeScript support
- Two way decoupled communication
Installation
$ npm i --save signals.ts
or
$ yarn add signal.ts
or
$ echo 'whatever package manager you use... the package is on npm.com 🙂'
Basic usage
//signals.ts --------------------------
import {Signal} from 'signals.ts';
export const MY_SIGNAL = new Signal();
export const MY_OTHER_SIGNAL = new Signal();
//...
// dispatcher.ts ----------------------
import {MY_SIGNAL} from './signals';
MY_SIGNAL.dispatch();
// listener.ts ------------------------
MY_SIGNAL.add(() => {
//do something about it
})
// or
MY_SIGNAL.addOnce(() => {
//do something about it just the first time the signal is received
})
Sending data
const SHOW_NOTIFICATION = new Signal<{title : string, body : string}>();
SHOW_NOTIFICATION.dispatch({title : 'Error', `There's a snake in your boot`});
Receiving data (asynchronously)
const SHOW_DIALOG = new Signal<{title : string, body : string, buttons : Array<'ok' | 'cancel' | 'yes' | 'no'>}>();
// the enquirer
const [response] = //response are always an array
await SHOW_DIALOG.dispatch({title : 'Danger', body : 'This button will destroy the world. Do you want to proceed?', buttons : ['yes', 'no']})
// the dialog view component
SHOW_DIALOG.add((config) => {
return new Promise(resolve => {
this.show(config)
.onResponse(resolve)
});
})
Controlling propagation
//any failed response, according to the listenerSuccessTest will stop propagation
const APP_WILL_CLOSE = new Signal({propagate : 'any-fail', listenerSuccessTest: val => val === true})
// The app controller
const responses = await APP_WILL_CLOSE.dispatch()
const shouldProceed = !response.some(dirty => dirty = false)
// any listening editors
APP_WILL_CLOSE.add(() => {
if(this.inABadMood) {
return false;
}
});
Retaining early signal dispatchment
export const APP_WAS_INITIALIZED = new Signal({memoize : true});
// app bootstrapping controller
await this.bootstrap()
APP_WAS_INITIALIZED.dispatch();
// some component we're not sure will be listening in time
// listener will be called even if addOnce is called after the
// signal was already dispatched
APP_WAS_INITIALIZED.addOnce(() => {
this.doSomething()
})
Detaching/Suspending a listener
Adding a listener returns a binding object that can be used to temporarily suspend or permanently detach the listener
const fn1Binding = SOME_SIGNAL.add(functionOne);
SOME_SIGNAL.add(functionTwo);
SOME_SIGNAL.dispatch() //will reach functionOne and functionTwo
fn1Binding.suspend()
SOME_SIGNAL.dispatch() //will reach functionTwo only
fn1Binding.resume()
SOME_SIGNAL.dispatch() //will reach functionOne and functionTwo
fn1Binding.detatch()
SOME_SIGNAL.dispatch() //will reach functionTwo only
fn1Binding.suspend() //will throw Error
fn1Binding.resume() //will throw Error
License
Running Tests
$ yarn
$ yarn test