Reactive, encrypted browser storage for Angular — built on Signals, under 2KB gzipped, zero runtime dependencies.
Also available for: React (npm · demo) · Vue (npm · demo)
Most storage wrappers give you a getter/setter pair and leave persistence, encryption, and expiry as an exercise for the consumer. ngx-local-vault collapses all three into a single WritableSignal: read it like any other signal, write it like any other signal, and the library takes care of encrypting the payload, syncing it to localStorage or sessionStorage, expiring it on a TTL, and staying inert during SSR.
- Signals-native —
watchSignal()returns a realWritableSignal<T>, no wrapper API to learn - Encrypted at rest — payloads are obfuscated before they ever touch the browser's storage
- TTL built in — pass
expiresIn: '1m'and the entry self-destructs, in-tab, without a reload - SSR-safe — guarded by
PLATFORM_ID, becomes a no-op persistence layer on the server - Zero dependencies —
@angular/coreand@angular/commonas peers, nothing else
npm i ngx-local-vaultSupports Angular 17, 18, 19, and 20.
import { ApplicationConfig } from '@angular/core';
import { provideVault } from 'ngx-local-vault';
export const appConfig: ApplicationConfig = {
providers: [
provideVault({ prefix: 'app_', encryptionKey: 'change-me', driver: 'local' })
]
};import { inject } from '@angular/core';
import { VaultService } from 'ngx-local-vault';
const vault = inject(VaultService);
const theme = vault.watchSignal<'light' | 'dark'>('theme', 'light');
theme.set('dark');
theme();
const session = vault.watchSignal<string | null>('session-token', null, { expiresIn: '15m' });
session.set('jwt-goes-here');expiresIn accepts ms, s, m, h, or d suffixes — '500ms', '30s', '15m', '2h', '1d'.
Live: https://ysndmr.github.io/ngx-local-vault/
projects/demo-app is a live showcase: a theme switcher backed by watchSignal(), and a TTL demo that saves a mock profile, shows the encrypted ciphertext sitting in localStorage next to the decrypted reactive value, and lets you watch it auto-delete after 60 seconds.
npm install
npm run build:lib
npm startnpm start serves demo-app; the library must be built first since the app imports ngx-local-vault from its built output (dist/ngx-local-vault), not the source directly — standard Angular library-workspace convention.
The encryption here is obfuscation, not a security boundary. The key lives in your app's bundle (or wherever you configure encryptionKey), so anyone with access to the running page — devtools, a browser extension, an XSS payload — can recover it just like they could any other client-side secret. This library does not, and cannot, protect data from an attacker who already controls the browser session.
What it does protect against is casual, incidental exposure: data no longer sits in localStorage as plain, human-readable JSON for a screen share, a saved HAR file, or a storage-reading browser extension to pick up at a glance. It raises the bar from "trivial to read" to "requires deliberate effort," nothing more.
Don't use this to store data you wouldn't be OK with the end user (or anything running in their browser) eventually reading — API secrets, other users' data, anything that needs real confidentiality. For that, encryption has to happen server-side, with a key the client never sees.
-
Log in to npm once, locally:
npm login
-
Build the library — this also strips
tslibfrom the publisheddependencies(ng-packagr adds it back on every build;scripts/patch-lib-package.mjsremoves it) so the zero-dependency badge stays honest:npm run build:lib
-
Dry-run the publish before it's live:
cd dist/ngx-local-vault npm publish --dry-run -
If the file list and
package.jsonlook right, publish for real:npm publish --access public
CI (.github/workflows/publish.yml) does this automatically on every push to main: it builds the library and the demo app, deploys the demo to GitHub Pages, and publishes to npm if an NPM_TOKEN secret is configured on the repository (Settings → Secrets and variables → Actions). No token, no publish step — the Pages deploy still runs.
MIT