An open API service indexing awesome lists of open source software.

https://github.com/zouloux/signal

Thin and simple entity-based event system
https://github.com/zouloux/signal

Last synced: 8 months ago
JSON representation

Thin and simple entity-based event system

Awesome Lists containing this project

README

          

# Signal

[//]: # (TODO : Image)

Thin and simple functional event system with strong typing.
Signal size is __![less than 300b](./bits/signal.es2017.min.js.svg)__ with __no external dependencies__.

Inspired from [Robert Penner](https://github.com/robertpenner)'s AS3 Signals.

Source code in Typescript, compiled to ESM & CJS Javascript thanks to [TSBundle](https://github.com/zouloux/tsbundle).
Works in Node and Browser environments.

---


Signal
Concept / 
Usage / 
Naming Signals / 
Remove / 
State Signal / 
Unpkg

---

### Concept

Classic event dispatcher systems are __string based__, which can be difficult to track across your application.

```typescript
document.addEventListener( "which one already ?", () => {} );
```

With Signal, every event is represented by an __entity__ with `add`, `remove` and `dispatch` methods.

Messages can be dispatched and followed __more fluently__ thanks to its dot notation.

```typescript
const onMessage = Signal()
onMessage.add( message => {
console.log( message ) // { from: "Michael", content: "Hello !" }
})
onMessage.dispatch({
from: "Michael",
content: "Hello !"
})
```

### Usage

Signal follow the [__composition over inheritance__](https://en.wikipedia.org/wiki/Composition_over_inheritance) concept of design patterns
to allow highly scalable projects and libraries. Ne need to extend EventDispatcher again.
Simple example of composition with several Signals :

```typescript
function createMessageSystem () { // No class, no inheritence, no pain
return {
// Two events -> two entities, no string used here
onConnected: Signal<[ Boolean ]>(), // Optional, can pass type of arguments
onMessage: Signal(), // No type here, so no check of passed object with TS

connect () {
// ...
onConnected.dispatch( true );
},
sendMessage ( userName:string, content:string ) {
// ...
onMessage.dispatch( { from: userName, content } )
}
}
}
const messageSystem = createMessageSystem();
messageSystem.onConnected.once( state => {
// Called once when connected
})
messageSystem.connect();
messageSystem.onMessage.add( message => {
console.log( message )
})
messageSystem.sendMessage("Bernie", "Hey")
// ...
messageSystem.sendMessage("Bernie", "What'up ?")

```

### Naming Signals

Signal are object entities which can and should be named correctly.
It's better to name signal prefixed with __"on"__ and with usage of preterit if possible.

```
✅ onMessage
✅ onMessageReceived
🚫 message
🚫 messageReceived
🚫 receiveMessage
---
✅ onConnected
✅ onData
✅ onDataSent
✅ onDataReceived
```

### Remove

Signal handlers can be detached with the remove function, but you need to keep track of the handler's reference.

```tsx
function handler () {
// Called once
}
onSignal.add( handler )
onSignal.dispatch()
// ...
onSignal.remove( handler ) // dettach listener
onSignal.dispatch()
```

For convenience and easier usage, when a signal is attached, a remove thunk is returned.
It allows fast removal of anonymous handlers without having to target it manually.

```tsx
const removeListener = onSignal.add(() => {
// Called once
})
onSignal.dispatch()
// ...
removeListener() // dettach listener without handler ref
onSignal.dispatch()
```

Works well with React Hooks :

```tsx
function ReactComponent ( props ) {
useLayoutEffect(() => {
// onData.add returns the remove function,
// so the layoutEffect will remove when component will be destroyed
return Model.onData.add( data => {

})
}, [])
return


}
```

Can be shortened to
```tsx
function ReactComponent ( props ) {
useLayoutEffect(() => Model.onData.add( data => {
// Data changed, listener will be removed automatically with component
}))
}
```

To clear all listeners. Useful to dispose a signal and allow garbage collection.

```tsx
onSignal.clear();
```

### State Signal

StateSignal is a kind of Signal which holds the last dispatched value.
A StateSignal can be initialized with a default value.

```tsx
// No need for generics here, state type is gathered from default value
const onStateSignal = StateSignal( 12 ) // 12 is the default value here
console.log(onStateSignal.state) // == 12

onStateSignal.add( value => {
// Is dispatched twice.
console.log( value )
// 1st -> 12 (call at init)
// 2nd -> 15 (dispatch)
}, true) // True here means "call at init" (will call handler when attached)

// Read and alter state
if ( onStateSignal.state === 12 )
onStateSignal.dispatch( 15 ) // Change the state value
```

State Signal will send old value as second argument. It can be useful to diff changes.

```tsx
const onStateSignal = StateSignal( 12 )
onStateSignal.add( ( newValue, oldValue ) => {
// Continue only when value actually changes
if ( newValue == oldValue )
return
if ( newValue > oldValue )
console.log("Greater")
else
console.log("Smaller")
})
onStateSignal.dispatch( 15 ) // Greater
onStateSignal.dispatch( 5 ) // Smaller
onStateSignal.dispatch( 5 ) // No effect
```

### Unpkg

Signal is available on [unpkg](https://unpkg.com/) CDN as :
- [Only Signal](https://unpkg.com/@zouloux/signal@latest/dist/signal.es2017.min.js) ![](./bits/signal.es2017.min.js.svg)
- [State-Signal + Signal.js](https://unpkg.com/@zouloux/signal@latest/dist/state-signal.es2017.min) ![](./bits/state-signal.es2017.min.js.svg)