@eviatarmor/indexed-signal
v1.0.1
Published
A writable Angular signal that persists to IndexedDB and synchronises across browser tabs
Maintainers
Readme
ngIndexedSignal
A lightweight Angular library that provides persistent, cross-tab synchronised signals using IndexedDB and BroadcastChannel APIs.
✨ Features
- Cross-tab synchronisation - Changes propagate instantly across all open tabs
- Automatic persistence - Signal values are automatically saved to IndexedDB
- Zero configuration - Works out of the box with sensible defaults
- Angular integration - Leverages Angular's dependency injection and lifecycle hooks
- Automatic cleanup - Resources are properly cleaned up when components are destroyed
🚀 Quick Start
import { Component } from '@angular/core';
import { indexedSignal } from 'ng-indexed-signal';
@Component({
selector: 'app-counter',
template: `
<div>
<p>Counter: {{ counter() }}</p>
<button (click)="increment()">Increment</button>
<button (click)="reset()">Reset</button>
</div>
`
})
export class CounterComponent {
// Create a persistent, synchronised signal
counter = indexedSignal(0, { key: 'app-counter' });
increment() {
this.counter.update(value => value + 1);
}
reset() {
this.counter.set(0);
}
}Open the same app in multiple tabs and watch them stay in sync! 🎉
💡 Usage Examples
User Preferences
interface UserPrefs {
theme: 'light' | 'dark';
language: string;
}
const prefs = indexedSignal<UserPrefs>(
{ theme: 'light', language: 'en' },
{ key: 'user-preferences' }
);
prefs.update(p => ({ ...p, theme: 'dark' }));Shopping Cart
interface CartItem {
id: string;
name: string;
quantity: number;
}
const cart = indexedSignal<CartItem[]>([], { key: 'shopping-cart' });
// Add item
cart.update(items => [...items, newItem]);
// Remove item
cart.update(items => items.filter(i => i.id !== itemId));Async Initialisation
async ngOnInit() {
// Wait for the signal to load from IndexedDB
await this.counter.waitUntilReady();
console.log('Loaded value:', this.counter());
}Custom Database
const mySignal = indexedSignal(
'initial',
{
key: 'my-key',
dbName: 'myCustomDB',
storeName: 'myCustomStore'
}
);Readonly Signals
class MyService {
private _count = indexedSignal(0, { key: 'count' });
// Expose readonly version
public readonly count = this._count.asReadonly();
increment() {
this._count.update(n => n + 1);
}
}🌐 Browser Support
Requires browsers that support:
- IndexedDB - Can I use IndexedDB?
- BroadcastChannel API - Can I use BroadcastChannel?
- Angular 14+
⚠️ Important Notes
- Each signal requires a unique key
- IndexedDB operations are asynchronous but non-blocking
- The
BroadcastChannelonly sends messages to other tabs, not back to itself - Cleanup is automatic when the injection context is destroyed (using RxJS)
🛠️ Installation
# Install dependencies
npm i @eviatarmor/indexed-signal📄 Licence
MIT © Eviatar Mor
🐛 Issues
Found a bug? Please open an issue.
