npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dgkit/signal-storage

v0.1.0

Published

Type-safe, bidirectional synchronization between Angular signals and localStorage/sessionStorage, with cross-tab sync.

Downloads

252

Readme

@dgkit/signal-storage

npm

Type-safe, bidirectional synchronization between an Angular signal and localStorage/sessionStorage.

const theme = injectStorageSignal('theme', 'light');

theme(); // 'light' — or whatever was already in storage
theme.set('dark'); // updates the signal and persists
theme.update((t) => (t === 'light' ? 'dark' : 'light'));
theme.remove(); // clear storage, revert to the default
  • Reads on init — seeded from any value already in storage
  • Persists on writeset()/update() write through automatically
  • Cross-tab sync — another tab changing the same key updates this signal too, via the native storage event (no echo loop: same-tab writes never trigger that event)
  • SSR-safe — behaves as in-memory-only state on the server; set() never throws, isSupported() reports false
  • Never throws — quota errors, private-browsing restrictions and corrupted stored JSON all degrade to a dev-mode warning, never a crash
  • Debounced writes — coalesce rapid updates into one storage write; a pending write is always flushed on destroy, never silently dropped
  • Pluggable serialization — swap JSON for a custom codec (Date, Map, anything)
  • ✅ Zoneless-friendly — no NgZone, no ChangeDetectorRef assumptions

Installation

yarn add @dgkit/signal-storage

Compatibility

Works with Angular 18, 19, 20 and 21.

| Dependency | Supported range | | ---------- | -------------------- | | Angular | >=18.0.0 <22.0.0 | | RxJS | ^6.5.3 or ^7.4.0 | | TypeScript | >=5.4 |

Angular and RxJS are peer dependencies — they are never bundled into the package.

Quick start

import { Component } from '@angular/core';
import { injectStorageSignal } from '@dgkit/signal-storage';

@Component({
  standalone: true,
  template: `
    <button (click)="theme.set(theme() === 'light' ? 'dark' : 'light')">
      Theme: {{ theme() }}
    </button>
  `,
})
export class ExampleComponent {
  // Field initializers run in an injection context, so this is all you need.
  protected readonly theme = injectStorageSignal('theme', 'light');
}

API

const value = injectStorageSignal(key, defaultValue, options?);

value();                 // Signal read — T
value.set(next);         // write — updates the signal and persists
value.update(updater);   // derive a new value from the current one
value.remove();          // clear the key, revert to defaultValue
value.isSupported();     // Signal<boolean> — false during SSR / when storage is unreachable

Options

| Option | Type | Default | Description | | ------------ | ------------------------- | ----------- | -------------------------------------------------------------------------------------------- | | storage | 'local' \| 'session' | 'local' | Which Storage object to use. | | serializer | { parse, stringify } | JSON | Custom (de)serialization for values JSON can't round-trip. | | syncTabs | boolean | true | Listen for the same key changing in another tab/window via the storage event. | | debounce | number | 0 | Debounce writes, in ms. 0 writes synchronously. A pending write is flushed on destroy. | | equal | (a: T, b: T) => boolean | Object.is | Equality used to skip redundant signal updates, both for local writes and cross-tab updates. |

Custom serializer example

const lastVisit = injectStorageSignal('lastVisit', new Date(0), {
  serializer: {
    parse: (raw) => new Date(raw),
    stringify: (value) => value.toISOString(),
  },
});

Behavior details

Cross-tab sync

By default, changes to the same key made in another tab or window are reflected here automatically. This relies on the native storage event, which the browser only fires in other tabs — the tab that made the change never receives it, so there's no risk of an update loop. Set syncTabs: false to opt out.

Errors never throw

  • Corrupted stored JSON (or a custom serializer that throws) on read: the signal falls back to defaultValue and logs a dev-mode warning.
  • Write failures (quota exceeded, Safari private-browsing restrictions): the signal still updates in memory; the write is dropped with a dev-mode warning instead of crashing the app.
  • Storage unreachable at all (SSR, or accessing localStorage throws): isSupported() is false and the signal behaves as ordinary in-memory state — set()/update()/remove() all still work, they just don't persist.

Debounce

  • debounce: 0 (default): every set()/update() writes to storage synchronously.
  • debounce > 0: writes within the window are coalesced — only the latest value is written, once. A write still pending at teardown is flushed immediately rather than lost.

Development

This package lives in the dgkit Nx monorepo.

yarn nx build signal-storage        # ng-packagr production build
yarn nx test signal-storage         # Vitest + coverage
yarn nx lint signal-storage         # ESLint
yarn nx typecheck signal-storage    # tsc --noEmit

Testing

Run with yarn nx test signal-storage. The suite covers initial reads, writes, debounce, cross-tab sync (via dispatched StorageEvents), custom serializers, and degraded/SSR environments. Coverage thresholds are enforced.

Contributing

Contributions are welcome — see the repository CONTRIBUTING guide.

License

MIT