@se-ng/signal-utils
v21.0.2
Published
This library provides some utilities to make it easier to work with signals in Angular.
Readme
SignalUtils
This library provides some utilities to make it easier to work with signals in Angular.
Installation
To install this library, run:
pnpm install @se-ng/signal-utils --saveUtilities (exports)
This section lists the package exports with brief one-line type indications, examples, and source links.
Reactive / Signals helpers ✅
asyncComputed— async helper to populate aSignalfrom promises, observables, or async-iterables.- Signature:
asyncComputed<T>(cb, initial?, destroyRef?)->Signal<T | initial | undefined> - Source:
src/reactive/async-computed.ts
Example:
const userSignal = asyncComputed(async () => { const res = await fetch('/api/user'); return await res.json(); }); // userSignal() === undefined initially, later contains the fetched object- Signature:
computedResource— resource wrapper exposing{ value: Signal, status, stream, error }.- Signature:
computedResource<T>(cb, initial?, destroyRef?)->Signal<Resource<T | initial | undefined>> - Source:
src/reactive/async-resource.ts
Example:
const resource = computedResource(async () => fetch('/items').then(r => r.json()) ); // access current value: resource().value(); resource().status- Signature:
debouncedSignal/debouncedComputed— debounce helpers for signals.debouncedSignal<T>(fn, {delay?, equal?}) => WritableSignal<T>debouncedComputed<T>(...) => Signal<T>- Source:
src/reactive/debounced-computed.ts
Example:
const debounced = debouncedSignal(() => searchTerm(), { delay: 200 }); // debounced.asReadonly() or debounced() to readinjectAwaitSignal/awaitSignal— wait for a signal to satisfy a predicate.awaitSignal<T>(signalFn, predicate) => Promise<T>injectAwaitSignal(injector?) => <T>(signalFn, predicate) => Promise<T>- Source:
src/reactive/await-signal.ts
Example:
await awaitSignal( () => readyFlag(), v => v === true ); // resolves when readyFlag() becomes true
Async & control helpers ✅
Deferred— a simple deferred promise container (useful for awaiting conditions).class Deferred<T> { promise: Promise<T>; resolve: (v: T) => void; reject: (e: any) => void; }- Source:
src/async/deferred.ts
Example:
const d = new Deferred<number>(); setTimeout(() => d.resolve(42), 100); const answer = await d.promise; // 42
HTTP client ✅
HttpActionClient— smallHttpClientwrapper exposing async methods and per-method busySignals.class HttpActionClient( methods returnPromise<any>;busyMethods: Record<Method, Signal<boolean>>)- Source:
src/http/http-action-client.ts
Example:
const data = await httpActionClient.get('/api/items'); if (httpActionClient.isBusy()) { /* show spinner */ }
Runtime type guards & helpers ✅
isAsyncIterable—isAsyncIterable(x): x is AsyncIterable<any>- Source:
src/guards/is-async-iterable.ts
Example:
if (isAsyncIterable(x)) { for await (const v of x) { console.log(v); } }- Source:
isPromise—isPromise<T>(x): x is Promise<T>- Source:
src/guards/is-promise.ts
Example:
async function handle(p: unknown) { if (isPromise(p)) { const value = await p; console.log(value); } }- Source:
isDate—isDate(x): x is Date- Source:
src/guards/is-date.ts
Example:
if (isDate(val)) { console.log(val.toISOString()); }- Source:
isObject—isObject<T>(x: T): boolean(object, not null/array/date)- Source:
src/guards/is-object.ts
Example:
if (isObject(obj)) { for (const key of Object.keys(obj)) { console.log(key, (obj as Record<string, unknown>)[key]); } }- Source:
isIntegerString—isIntegerString(str): boolean(digits-only integer check)- Source:
src/guards/is-integer-string.ts
Example:
if (isIntegerString('01')) { const idx = parseInt('01', 10); // use idx to index into an array safely }- Source:
Deep & structural utilities ✅
cloneDeep— deep clone handling circular refs, Maps, Sets, Dates, RegExp.cloneDeep<T>(value: T): T- Source:
src/deep/clone-deep.ts
Example:
const copy = cloneDeep(original); // no shared referencesdeepEqual— deep structural equality check for: objects, arrays, maps, sets, dates and typed arrays.deepEqual(a, b): boolean- Source:
src/deep/deep-equal.ts
Example:
deepEqual({ a: 1 }, { a: 1 }) // truemergeDeep— deep merge with configurable iterable strategies (concat|replace|merge).mergeDeep<A,B>(target:A, source:B, options?) => DeepMergeObjects<A,B>- Source:
src/deep/merge-deep.ts
Example:
mergeDeep({ items: [1] }, { items: [2] }, { iterableMergeStrategy: 'merge' }); // -> { items: [2] } (source values at indices take precedence)deepDiff— compute a minimal patch object with changed keys between two objects.deepDiff<T>(a: T, b: T): Partial<T>(reconstructed patch)- Source:
src/deep/deep-diff.ts
Example:
deepDiff({a:1,b:2}, {a:1,b:3}) // -> { b: 3 }
Path & flatten utilities ✅
objFromPath— builds an object or array from a dot/bracket path and a value.objFromPath<T>(path: string, value?): T- Source:
src/path/obj-from-path.ts
Example:
objFromPath('a[0].b', 5) // -> { a: [ { b: 5 } ] }flattenRecord/unFlattenRecord— flatten/unflatten nested objects/arrays to/from dot-path records.flattenRecord(obj, {onCircular?, marker?}) => Record<string, any>unFlattenRecord(record) => Record<string, any>- Source:
src/path/flatten-record.ts,src/path/un-flattenRecord.ts
Example:
flattenRecord({ user: { name: 'Alice' } }); // -> { 'user.name': 'Alice' } unFlattenRecord({ 'user.name': 'Alice' }); // -> { user: { name: 'Alice' } }
Notes & Edge Cases ⚠️
Guards:
isPromisedetects thenables by checking for a.thenmethod. Objects that merely implementthenwill be considered promises.isAsyncIterablechecks forSymbol.asyncIteratorand will only return true for true async iterables.isDatechecks for agetMonthmethod; objects exposing a similarly named function may be misclassified in fringe cases.
flattenRecord/unFlattenRecord:flattenRecordcan mark or throw on circular references depending on theonCircularoption.unFlattenRecorduses integer-like path segments to build arrays; it will produce sparse arrays when indices are non-contiguous.
deepDiff:- The function produces a reconstructed patch containing only changed keys. For arrays it will reconstruct sparse changes (only changed indices are present) — be mindful when applying patches or merging results.
mergeDeep:- Array/iterable strategy options (
concat,replace,merge) change behavior substantially:mergeperforms index-wise merging (source indices take precedence)concatappendsreplaceoverwrites
- Merging
SetorMapwith a non-matching type will throw unlessreplaceis used.
- Array/iterable strategy options (
cloneDeep/deepEqual:cloneDeeppreserves circular references and copies Dates, RegExp and TypedArrays appropriately. Functions and prototype chains are not deeply cloned.deepEqualcovers most builtin types (including Maps/Sets) but can be surprised by objects with customvalueOf/toStringbehavior.
Async helpers (
asyncComputed,computedResource):- Both use an
AbortControllerfor cancellation when re-run and rely on aDestroyReffor cleanup. When used outside injection context you must provide aDestroyRefexplicitly.
- Both use an
HttpActionClient:- Per-method busy counters are incremented before the request and
decremented in
finally. ConsultbusyMethodsto reflect per-method busy state in the UI.
- Per-method busy counters are incremented before the request and
decremented in
Deferred:- Handy for externally resolving a promise — remember to resolve or reject to avoid leaving pending promises and potential memory leaks.
License
This project is licensed under the MIT License - see the LICENSE file for details.
