ServiceUpdated in v15

WrStorage

Typed, reactive key-value storage over localStorage or any engine, safe under SSR.

Installation

Import the class. This entry point has no stylesheet.

import { WrStorage } from 'ngwr/storage';

Usage

private readonly store = inject(WrStorage);

this.store.set('user', { name: 'Ada' });
this.store.get<{ name: string }>('user'); // { name: 'Ada' }

this.store.set('cart', items, { ttl: 60_000 }); // gone after 60 s
this.store.has('cart'); // ignores expiry

const theme = this.store.watch<'light' | 'dark'>('theme', 'light');
effect(() => console.log('theme is', theme()));

Live demo

Reactive read via store.watch('demo:visits'): 0

TTL (10s) — stash a note then refresh: — empty —

Open this page in two tabs and click Bump visits in one — the other updates via the storage event.

Swap the engine

Pass an engine to withStorage() at bootstrap; WrStorage reads it once.

provideWr(withStorage({ engine: sessionStorage }))

// A factory, for a wrapper such as encrypted storage:
provideWr(withStorage({ engine: () => new EncryptedStorage(localStorage, key) }))

// Or the token, at bootstrap only:
providers: [{ provide: WR_STORAGE_ENGINE, useValue: sessionStorage }]

What lands in storage

set() stores a JSON envelope: the value under v, plus an expiry e in epoch ms when a TTL applies.

import type { WrStorageEnvelope } from 'ngwr/storage';

// provideWr(withStorage({ prefix: 'myapp:' }))
store.set('theme', 'dark');
// localStorage['myapp:theme'] === '{"v":"dark"}'

store.set('cart', [{ sku: 'a1' }], { ttl: 60_000 });
// localStorage['myapp:cart'] === '{"v":[{"sku":"a1"}],"e":1767225600000}'

// Reading it outside Angular:
const raw = localStorage.getItem('myapp:theme');
const env = raw === null ? null : (JSON.parse(raw) as WrStorageEnvelope<string>);
const value = env && (env.e === undefined || env.e >= Date.now()) ? env.v : null;
  • The raw key is prefix + key; code outside Angular has to add the prefix too.
  • A key that holds no envelope is returned as stored.
  • json: false stores String(value) and drops TTL.

API

NameDescriptionTypeDefault
get(key, fallback?)Read a value. Returns fallback (default null) when absent or expired.<T>(k: string, f?: T) => T | null—
set(key, value, opts?)Write a value. ttl (ms) overrides the configured default.<T>(k: string, v: T, opts?: { ttl?: number }) => void—
remove(key)Remove the value at key.(k: string) => void—
clear()Clear every key under the prefix, or every key when there is none.() => void—
has(key)Whether the key exists, ignoring expiry.(k: string) => boolean—
keys()All known keys with the prefix stripped.() => readonly string[]—
watch(key, fallback?)Read as a signal. Follows writes from this tab and others.<T>(k: string, f?: T) => Signal<T | null>—
withStorage(options?)The provideWr() feature: prefix, json, ttl and engine.(options?: WrStorageOptions) => WrFeature<'storage'>—
WR_STORAGE_ENGINEThe active Storage engine. Provide it at bootstrap; a route or component provider is ignored unless it also provides WrStorage.InjectionToken<Storage>localStorage / memory fallback
createMemoryStorage()An in-memory Storage, for tests.() => Storage—
WrStorageEnvelopeThe stored format while json is on: value v, expiry e in epoch ms.{ readonly v: T; readonly e?: number }—

Dependencies

See also