Gathering detailed insights and metrics for signal-utils
Gathering detailed insights and metrics for signal-utils
Gathering detailed insights and metrics for signal-utils
Gathering detailed insights and metrics for signal-utils
@zag-js/dismissable
Dismissable layer utilities for the DOM
@dotted-labs/ngx-signal-utils
<p > <a href="https://www.npmjs.com/package/@dotted-labs/ngx-signal-utils"> <img src="https://img.shields.io/npm/v/@dotted-labs/ngx-signal-utils" alt="NPM"> </a> <a href="https://www.npmjs.com/package/@dotted-labs/ngx-signal-utils"> <img src
@se-ng/signal-utils
This library provides some utilities to make it easier to work with signals in Angular.
signal-control-utils
Common utilities for signal processing and control based on NodeJS
npm install signal-utils
Typescript
Module System
Node Version
NPM Version
v0.21.1-signal-utils
Updated on Dec 23, 2024
v0.21.0-signal-utils
Updated on Dec 10, 2024
v0.20.0-signal-utils
Updated on Oct 08, 2024
v0.19.0-signal-utils
Updated on Oct 02, 2024
v0.18.0-signal-utils
Updated on Jul 03, 2024
v0.17.0-signal-utils
Updated on Jul 03, 2024
TypeScript (100%)
Total Downloads
0
Last Day
0
Last Week
0
Last Month
0
Last Year
0
MIT License
106 Stars
113 Commits
12 Forks
9 Watchers
3 Branches
12 Contributors
Updated on Jun 07, 2025
Latest Version
0.21.1
Package Id
signal-utils@0.21.1
Unpacked Size
335.71 kB
Size
88.47 kB
File Count
98
NPM Version
10.5.1
Node Version
22.0.0
Published on
Dec 23, 2024
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
1
Utils for the Signal's Proposal.
Try it out on JSBin: https://jsbin.com/safoqap/edit?html,output
signal-utils
is (currently) a place to experiment and see what works and what gaps there may be with the polyfill. Have an idea? feel free to submit a PR!
Like the signals polyfill, this library is not meant to be used in production1.
1npm add signal-utils signal-polyfill
[!NOTE] As of now, the Signals proposal isn't part of JavaScript, so you'll need to use a polyfill. See signal-utils's peerDependency section in the package.json for the supported version range.
[!NOTE] All examples either use JavaScript or a mixed-language psuedocode2 to convey the reactive intention of using Signals. These utilities can be used in any framework that wires up Signals to their rendering implementation.
@signal
A utility decorator for easily creating signals
1import { signal } from 'signal-utils'; 2 3class State { 4 @signal accessor #value = 3; 5 6 get doubled() { 7 return this.#value * 2; 8 } 9 10 increment = () => this.#value++; 11} 12 13let state = new State(); 14 15 16// output: 6 17// button clicked 18// output: 8 19<template> 20 <output>{{state.doubled}}</output> 21 <button onclick={{state.increment}}>+</button> 22</template>
This utility decorator can also be used for caching getters in classes. Useful for caching expensive computations.
1import { signal } from 'signal-utils'; 2 3class State { 4 @signal accessor #value = 3; 5 6 // NOTE: read-only because there is no setter, and a setter is not allowed. 7 @signal 8 get doubled() { 9 // imagine an expensive operation 10 return this.#value * 2; 11 } 12 13 increment = () => this.#value++; 14} 15 16let state = new State(); 17 18 19// output: 6 20// button clicked 21// output: 8 22<template> 23 <output>{{state.doubled}}</output> 24 <button onclick={{state.increment}}>+</button> 25</template>
Note that the impact of maintaining a cache is often more expensive than re-deriving the data in the getter. Use sparingly, or to return non-primitive values and maintain referential integrity between repeat accesses.
@deepSignal
A utility decorator for recursively, deeply, and lazily auto-tracking JSON-serializable structures at any depth or size.
1import { deepSignal } from 'signal-utils/deep'; 2 3class Foo { 4 @deepSignal accessor obj = {}; 5} 6 7let instance = new Foo(); 8let setData = () => instance.obj.foo = { bar: 3 }; 9let inc = () => instance.obj.foo.bar++; 10 11<template> 12 {{instance.obj.foo.bar}} 13 14 <button onclick={{setData}}>Set initial data</button> 15 <button> onclick={{inc}}>increment</button> 16</template>
Note that this can be memory intensive, and should not be the default way to reach for reactivity. Due to the nature of nested proxies, it's also much harder to inspect.
Inspiration for deep reactivity comes from:
deep
functionA utility function for recursively, deeply, and lazily auto-tracking JSON-serializable structures at any depth or size.
1import { deep } from 'signal-utils/deep'; 2 3let obj = deep({}); 4let setData = () => obj.foo = { bar: 3 }; 5let inc = () => obj.foo.bar++; 6 7<template> 8 {{obj.foo.bar}} 9 10 <button onclick={{setData}}>Set initial data</button> 11 <button> onclick={{inc}}>increment</button> 12</template>
Note that this can be memory intensive, and should not be the default way to reach for reactivity. Due to the nature of nested proxies, it's also much harder to inspect.
@localCopy
A utility decorator for maintaining local state that gets re-set to a "remote" value when it changes. Useful for editable controlled fields with an initial remote data that can also change.
1import { signal } from 'signal-utils'; 2import { localCopy } from 'signal-utils/local-copy'; 3 4class Remote { 5 @signal accessor value = 3; 6} 7 8class Demo { 9 // pretend this data is from a parent component 10 remote = new Remote(); 11 12 @localCopy('remote.value') localValue; 13 14 updateLocalValue = (inputEvent) => this.localValue = inputEvent.target.value; 15 16 // A controlled input 17 <template> 18 <label> 19 Edit Name: 20 <input value={{this.localValue}} oninput={{this.updateLocalValue}} /> 21 </label> 22 </template> 23}
In this demo, the localValue can fork from the remote value, but the localValue
property will re-set to the remote value if it changes.
localCopy
function1import { Signal } from 'signal-polyfill'; 2import { localCopy } from 'signal-utils/local-copy'; 3 4const remote = new Signal.State(3); 5 6const local = localCopy(() => remote.get()); 7const updateLocal = (inputEvent) => local.set(inputEvent.target.value); 8 9// A controlled input 10<template> 11 <label> 12 Edit Name: 13 <input value={{local.get()}} oninput={{updateLocal}} /> 14 </label> 15</template>
Live, interactive demos of this concept:
Array
A reactive Array. This API mimics the built-in APIs and behaviors of Array.
1import { SignalArray } from 'signal-utils/array'; 2 3let arr = new SignalArray([1, 2, 3]); 4 5// output: 3 6// button clicked 7// output: 2 8<template> 9 <output>{{arr.at(-1)}}</output> 10 <button onclick={{() => arr.pop()}}>pop</button> 11</template>
Other ways of constructing an array:
1import { SignalArray, signalArray } from 'signal-utils/array'; 2 3SignalArray.from([1, 2, 3]); 4signalArray([1, 2, 3]);
Note that .from
gives you more options of how to create your new array structure.
Object
A reactive Object. This API mimics the built-in APIs and behaviors of Object.
1import { SignalObject } from 'signal-utils/object'; 2 3let obj = new SignalObject({ 4 isLoading: true, 5 error: null, 6 result: null, 7}); 8 9// output: true 10// button clicked 11// output: false 12<template> 13 <output>{{obj.isLoading}}</output> 14 <button onclick={{() => obj.isLoading = false}}>finish</button> 15</template>
In this example, we could use a reactive object for quickly and dynamically creating an object of signals -- useful for when we don't know all the keys boforehand, or if we want a shorthand to creating many named signals.
Other ways of constructing an object:
1import { SignalObject, signalObject } from 'signal-utils/object'; 2 3SignalObject.fromEntries([ /* ... */ ]); 4signalObject({ /* ... */ } );
Note that .fromEntries
gives you more options of how to create your new object structure.
Map
A reactive Map
1import { SignalMap } from 'signal-utils/map'; 2 3let map = new SignalMap(); 4 5map.set('isLoading', true); 6 7// output: true 8// button clicked 9// output: false 10<template> 11 <output>{{map.get('isLoading')}}</output> 12 <button onclick={{() => map.set('isLoading', false)}}>finish</button> 13</template>
WeakMap
A reactive WeakMap
1import { SignalWeakMap } from 'signal-utils/weak-map'; 2 3let map = new SignalWeakMap(); 4 5let obj = { greeting: 'hello' }; 6 7map.set(obj, true); 8 9// output: true 10// button clicked 11// output: false 12<template> 13 <output>{{map.get(obj)}}</output> 14 <button onclick={{() => map.set(obj, false)}}>finish</button> 15</template>
Set
A reactive Set
1import { SignalSet } from 'signal-utils/set'; 2 3let set = new SignalSet(); 4 5set.add(123); 6 7// output: true 8// button clicked 9// output: false 10<template> 11 <output>{{set.has(123)}}</output> 12 <button onclick={{() => set.delete(123)}}>finish</button> 13</template>
WeakSet
A reactive WeakSet
1import { SignalWeakSet } from 'signal-utils/weak-set'; 2 3let set = new SignalWeakSet(); 4 5let obj = { greeting: 'hello' }; 6 7set.add(obj); 8 9// output: true 10// button clicked 11// output: false 12<template> 13 <output>{{set.has(obj)}}</output> 14 <button onclick={{() => set.delete(obj)}}>finish</button> 15</template>
Promise
A reactive Promise handler that gives your reactive properties for when the promise resolves or rejects.
1import { SignalAsyncData } from 'signal-utils/async-data'; 2 3const response = fetch('...'); 4const signalResponse = new SignalAsyncData(response); 5 6// output: true 7// after the fetch finishes 8// output: false 9<template> 10 <output>{{signalResponse.isLoading}}</output> 11</template>
There is also a load
export which does the construction for you.
1import { load } from 'signal-utils/async-data'; 2 3const response = fetch('...'); 4const signalResponse = load(response); 5 6// output: true 7// after the fetch finishes 8// output: false 9<template> 10 <output>{{signalResponse.isLoading}}</output> 11</template>
the signalResponse
object has familiar properties on it:
value
error
state
isResolved
isPending
isRejected
The important thing to note about using load
/ SignalAsyncData
, is that you must already have a PromiseLike
. For reactive-invocation of async functions, see the section below on signalFunction
async
function
A reactive async function with pending/error state handling
1import { Signal } from 'signal-polyfill'; 2import { signalFunction } from 'signal-utils/async-function'; 3 4const url = new Signal.State('...'); 5const signalResponse = signalFunction(async () => { 6 const response = await fetch(url.get()); // entangles with `url` 7 // after an away, you've detatched from the signal-auto-tracking 8 return response.json(); 9}); 10 11// output: true 12// after the fetch finishes 13// output: false 14<template> 15 <output>{{signalResponse.isLoading}}</output> 16</template> 17
the signalResponse
object has familiar properties on it:
value
error
state
isResolved
isPending
isRejected
isError
(alias)isSettled
(alias)isLoading
(alias)isFinished
(alias)retry()
dedupe
+ @dedupe
wip
utilities for the dedupe pattern.
wip
Forking a reactive tree and optionally sync it back to the original -- useful for forms / fields where you want to edit the state, but don't want to mutate the reactive root right away.
Inspo: https://github.com/chriskrycho/tracked-draft
Utilities that can easily lead to subtle bugs and edge cases.
1import { Signal } from 'signal-polyfill'; 2import { effect } from 'signal-utils/subtle/microtask-effect'; 3 4let count = new Signal.State(0); 5 6let callCount = 0; 7 8effect(() => console.log(count.get()); 9// => 0 logs 10 11count.set(1); 12// => 1 logs
A reaction tracks a computation and calls an effect function when the value of the computation changes.
1import { Signal } from 'signal-polyfill'; 2import { reaction } from 'signal-utils/subtle/reaction.js'; 3 4const a = new Signal.State(0); 5const b = new Signal.State(1); 6 7reaction( 8 () => a.get() + b.get(), 9 (value, previousValue) => console.log(value, previousValue) 10); 11 12a.set(1); 13// after a microtask, logs: 2, 1
Sometimes it may be useful to run an effect synchronously after updating signals. The proposed Signals API intentionally makes this difficult, because signals are not allowed to be read or written to within a watcher callback, but it is possible as long as signals are accessed after the watcher notification callbacks have completed.
batchedEffect()
and batch()
allow you to create effects that run synchronously at the end of a batch()
callback, if that callback updates any signals the effects depend on.
1const a = new Signal.State(0); 2const b = new Signal.State(0); 3 4batchedEffect(() => { 5 console.log("a + b =", a.get() + b.get()); 6}); 7 8// Logs: a + b = 0 9 10batch(() => { 11 a.set(1); 12 b.set(1); 13}); 14 15// Logs: a + b = 2
Synchronous batched effects can be useful when abstracting over signals to use them as a backing storage mechanism. In some cases you may want the effect of a signal update to be synchronously observable, but also to allow batching when possible for the usual performacne and coherence reasons.
The AsyncComputed
class reprents an async computation that consumes other signals.
While computing a value based on other signals synchronously is covered by the core signals API, computing a value asynchronously is not. (There is an ongoing [discussion about how to handle async computations](https://github.com/tc39/proposal-signals/issues/30 however).
AsyncComputed
is similar to Signal.Computed
, except that it takes an async (or Promise-returning) function as the computation function.
All synchronous access to signals within the function is tracked (by running it within a Signal.Computed
), so that when the signal dependencies change, the computation is rerun. New runs of the async computation preempt pending runs of the computation.
1import {AsyncComputed} from 'signal-utils/async-computed'; 2 3const count = new Signal.State(1); 4 5const asyncDoubled = new AsyncComputed(async () => { 6 // Wait 10ms 7 await new Promise((res) => setTimeout(res, 10)); 8 9 return count.get() * 2; 10}); 11 12console.log(asyncDoubled.status); // Logs: pending 13console.log(asyncDoubled.value); // Logs: undefined 14 15await asyncDoubled.complete; 16 17console.log(asyncDoubled.status); // Logs: complete 18console.log(asyncDoubled.value); // Logs: 2
An AsyncComputed
instance tracks its "status", which is either "initial"
,
"pending"
, "complete"
, or "error"
.
constructor<T>(fn, options)
fn: (abortSignal: AbortSignal) => Promise<T>
: The compute function.
Synchronous signal access (before the first await) is tracked.
If a run is preempted by another run because dependencies change, the
AbortSignal will abort. It's recomended to call signal.throwIfAborted()
after any await
.
options?: AsyncComputedOptions<T>
:
initialValue
: The initial value to return from .value
before the
computation has yet run.status: "initial" | "pending" | "complete" | "error"
value: T | undefined
: The last value that the compute function resolved
with, or undefined
if the last run of the compute function threw an error.
If the compute function has not yet been run value
will be the value of the
initialValue
or undefined
.
error: unknown
: The last error that the compute function threw, or
undefined
if the last run of the compute function resolved successfully, or
if the compute function has not yet been run.
complete: Promise<T>
: A promise that resolves when the compute function has
completed, or rejects if the compute function throws an error.
If a new run of the compute function is started before the previous run has completed, the promise will resolve with the result of the new run.
run(): void
: Runs the compute function if it is not already running and its
dependencies have changed.
get(): T | undefined
: Retruns the current value
or throws if the last
completion result was an error. This method is best used for accessing from
other computed signals, since it will propagate error states into the other
computed signals.
See: ./CONTRIBUTING.md
Likely not, code (and tests!) are copied from pre-existing implementations, and those implementations change over time. If you find a bug, please file an issue or open a PR, thanks!!
This library could not have been developed so quickly without borrowing from existing libraries that already built these patterns. This library, signal-utils, is an adaptation and aggregation of utilities found throughout the community.
tracked-built-ins
tracked-toolbox
ember-async-data
reactiveweb
tracked-draft
The releases and versions specified by this package and npm are not an indicator of production readiness, nor stabilitiy. But releases do follow semver. ↩
The syntax is based of a mix of Glimmer-flavored Javascript and Svelte. The main thing being focused around JavaScript without having a custom file format. The <template>...</template>
blocks may as well be HTML, and {{ }}
escapes out to JS. I don't have a strong preference on {{ }}
vs { }
, the important thing is only to be consistent within an ecosystem. ↩
No vulnerabilities found.
No security vulnerabilities found.