IN DEVELOPMENT
Minimal signals like library built with powerful browser features; EventTarget and AsyncIterators.
import { Signal } from '@heymp/signals';
const count = new Signal.State(0);
const isDone = new Signal.Computed(() => count.value > 3, [count]);
setInterval(() => {
count.value++;
}, 1000)
for await (const value of isDone) {
console.log('Counter is done:', value);
}
See the Signals Todo MVC example.
Signals are based on the EventTarget
interface. This means you can listen to the update
event emitted from that signal.
const count = new Signal.State(0);
count.addEventListener('update', () => {
console.log('Count updated:', count.value);
});
Signals also contain a @@asyncIterator.
Async iterables are objects with a [Symbol.asyncIterator]
method, which
returns an iterator who's next()
method returns a Promise. This allows you
to use the for await...of
syntax to listen to the signal.
NOTE: Using an async iterator in a for await...of
loop will block the event loop
until the signal is done emitting values.
const count = new Signal.State(0);
for await (const value of count) {
console.log('Count updated:', value);
}
The cool thing about using AsyncIterators is that frameworks can expose methods for re-rendering components when new values are yielded.
For instance, Lit exposes asyncReplace
and asyncAppend
directives
to easily render new values from an async iterable.
import { LitElement, html } from 'lit'
import { asyncReplace } from 'lit/directives/async-replace.js'
export class MyElement extends LitElement {
render() {
return html`
<button @click=${() => store.counter.value++}>
count is ${asyncReplace(store.counter)}
</button>
`;
}
}
This library exposes framework specific adapters to make it easy to sync changes of the Signals to trigger the component render lifecycle.
import { Signal } from '@heymp/signals';
import { useSignal } from '@heymp/signals/react';
const counter = new Signal.State(0);
function App() {
const [count] = useSignal(counter);
return (
<>
<button onClick={() => counter.value++}>
count is {count}
</button>
</>
);
}
import { LitElement, html } from 'lit'
import { State } from '@heymp/signals';
import { watchSignal } from '@heymp/signals/lit';
export class MyCounter extends LitElement {
@watchSignal
private count?: State<number>;
@watchSignal
private message = new State('hello, world');
render() {
return html`
${this.message.value} ${this.count?.value}
`
}
}
There has been some awesome work done by Rob Eisenberg and library authors to propose Signals as a new TC39 standard. I highly recommend reading this article to learn more about this initiative.
This is mostly a proof of concept but we do use this pattern for some of our internal projects. There are known issues with EventTarget if there are thousands of EventTarget instances.
I would recommend looking at the Signal Polyfill as an alternative.