@feavy/signal
v1.1.0
Published
Reactive signals for JavaScript [WIP]
Downloads
10
Readme
Signal
Still under development.
Signal is a lightweight library that allows to easily add reactivity to programs, so they can react to variable updates in real-time.
Inspired by reactor.js and solid, Signal is based on a system of signals and observers.
Here is a simple example of how to use it:
import Signal from '@feavy/signal/Signal';
import { observe } from '@feavy/signal/Observer';
interface Vec2 { x: number; y: number; }
const position: Signal<Vec2> = new Signal({x: 0, y: 0});
observe(() => {
console.log("Position:", position.x, position.y);
}); // Prints: "Position: 0 0"
position.x = 10; // Prints: "Position: 10 0"
position.value = {x: 5, y :5}; // Prints: "Position: 0 0"
Each time position
is modified, its observers are triggered.
- Signals are like normal objects, you can access their properties, or the value itself with the
value
attribute. - When an observer accesses a signal, it is automatically registered.
- Observers are triggered when the modified properties are exactly the ones observed.
Signal
is designed to be unobtrusive and offer a very simple API.
- Each variable can easily be replaced by a signal, especially class properties that can use getters and setters to modify the signal's value.
- TODO: There are several ways to observe signals.
- TODO: Signals can be chained to create complex behaviors.
It is important to know that a signal is not a stream
: it is not possible to access previous values. So, for example, accumulation is not possible. A signal represent one value at a time.
However, streams may be added in a future version.
Usage
Declaring a Signal
Signals are the basis of this library and represent any variable you want to make observable.
For example, in a video game, you may want to make the health value observable.
You would write something like this:
const health = new Signal(20);
observe(() => {
console.log("HP:", health.value);
});
The function will be called every time health.value
is changed.
Inside a class it is more convenient to keep the signal private and only expose its value
attribute:
class Entity {
private readonly _health: Signal<number> = new Signal(20);
public set health(health: number) { this._health.value = health; }
public get health(): Observable<number> { return this._health.value; }
}
const entity = new Entity();
observe(() => {
console.log("Entity HP:", entity.health);
});
In this way entity.health
acts as a normal integer property, which can be observed.
Object value
It is also possible to make an object observable.
For example:
const position = new Signal({x: 0, y: 0});
Here position.value
will be an object like :
Signal<{
x: Observable<number>,
y: Observable<number>
}>
Also note that the signal acts as a proxy for every object property. So instead of writing position.value.x
, you can simply write position.x
, which will return the same value.
Nested objects
In the case of nested objects, if you access an object attribute, you actually get a signal that matches the object’s interface:
const state = new Signal({position: {x: 0, y: 0}, moving: false});
state.value
:
Signal<{
position: Signal<{
x: Observable<number>,
y: Observable<number>
}>,
moving: Observable<boolean>
}>
You can use the utility property state.value.unwrapped
to get the underlying object (not observable):
{
position: {
x: number,
y: number
},
moving: boolean
}