A comprehensive Angular library for browser storage management with TypeScript support, reactive APIs, encryption, TTL, and more.
- 🔐 Encryption - AES-GCM encryption via Web Crypto API
- ⏰ TTL Support - Auto-expire stored data
- 🔄 Reactive - Observable and Signal-based APIs
- 🌐 Cross-tab Sync - localStorage changes sync across tabs
- 📦 Multiple Storage Types - localStorage, sessionStorage, cookies, IndexedDB
- 🎯 Type-Safe - Full TypeScript support
- 🔑 Namespacing - Prefix keys for isolation
- 🛡️ Fallback Strategy - Automatic storage fallback
- 📊 Smart Serialization - Handles Date, Map, Set, BigInt
npm install ngx-webstore// app.module.ts
import { NgxWebStoreModule } from 'ngx-webstore';
@NgModule({
imports: [
NgxWebStoreModule.forRoot({
namespace: 'myApp',
defaultTTL: 86400000, // 24 hours
encryption: {
enabled: true,
secret: environment.storageSecret
}
})
]
})
export class AppModule {}// component.ts
import { LocalStorageService } from 'ngx-webstore';
@Component({...})
export class MyComponent {
constructor(private localStorage: LocalStorageService) {}
async saveUser() {
await this.localStorage.set('user', { name: 'John', age: 30 });
}
async getUser() {
const user = await this.localStorage.get<User>('user');
console.log(user);
}
}Injectable service for browser localStorage with cross-tab synchronization.
import { LocalStorageService } from 'ngx-webstore';
constructor(private localStorage: LocalStorageService) {}Methods:
| Method | Return Type | Description |
|---|---|---|
get<T>(key: string) |
Promise<T | null> |
Get value by key |
set<T>(key: string, value: T, options?) |
Promise<void> |
Set value with options |
remove(key: string) |
Promise<void> |
Remove a key |
clear() |
Promise<void> |
Clear all keys in namespace |
keys() |
Promise<string[]> |
Get all keys |
has(key: string) |
Promise<boolean> |
Check if key exists |
watch<T>(key: string) |
Observable<T | null> |
Watch for changes |
Properties:
isAvailable: boolean- Check if localStorage is available
Examples:
// Basic usage
await this.localStorage.set('theme', 'dark');
const theme = await this.localStorage.get<string>('theme');
// With TTL (1 hour)
await this.localStorage.set('token', 'abc123', { ttl: 3600000 });
// With encryption
await this.localStorage.set('sensitive', { ssn: '123-45-6789' }, { encrypt: true });
// Reactive updates
this.localStorage.watch<string>('theme').subscribe(theme => {
console.log('Theme changed:', theme);
});
// Check if exists
const exists = await this.localStorage.has('user');
// Get all keys
const keys = await this.localStorage.keys();Injectable service for browser sessionStorage. Same API as LocalStorageService but data persists only for session duration.
import { SessionStorageService } from 'ngx-webstore';
constructor(private sessionStorage: SessionStorageService) {}
// API identical to LocalStorageService
await this.sessionStorage.set('tempData', { id: 123 });Injectable service for HTTP cookies with full cookie options support.
import { CookieService } from 'ngx-webstore';
constructor(private cookieService: CookieService) {}Extended Options:
interface CookieStorageOptions {
ttl?: number; // Time-to-live in milliseconds
encrypt?: boolean; // Encrypt cookie value
path?: string; // Cookie path (default: '/')
domain?: string; // Cookie domain
secure?: boolean; // Secure flag
sameSite?: 'Strict' | 'Lax' | 'None'; // SameSite attribute
}Examples:
// Basic usage
await this.cookieService.set('user', { id: 1 });
// With all options
await this.cookieService.set('authToken', 'abc123', {
ttl: 3600000, // 1 hour
secure: true, // HTTPS only
sameSite: 'Strict', // CSRF protection
path: '/admin', // Specific path
encrypt: true // Encrypted
});
// Get cookie
const token = await this.cookieService.get<string>('authToken');Note: Cookies have a ~4KB size limit. The service will throw an error if the data exceeds this limit.
Injectable service for IndexedDB storage. Best for large data storage.
import { IndexedDBService } from 'ngx-webstore';
constructor(private indexedDB: IndexedDBService) {}Examples:
// Store large objects
await this.indexedDB.set('largeDataset', {
items: [...], // Large array
metadata: {...}
});
// Retrieve
const data = await this.indexedDB.get<Dataset>('largeDataset');
// Same API as other storage services
await this.indexedDB.remove('key');
const keys = await this.indexedDB.keys();Configuration:
NgxWebStoreModule.forRoot({
indexedDB: {
dbName: 'my-app-db',
storeName: 'storage',
version: 1
}
})Injectable service for reactive global application state with optional persistence.
import { GlobalStateService } from 'ngx-webstore';
constructor(private globalState: GlobalStateService) {}Methods:
| Method | Return Type | Description |
|---|---|---|
createVar<T>(key, defaultValue, options?) |
GlobalVariable<T> |
Create reactive variable |
getVar<T>(key) |
GlobalVariable<T> | undefined |
Get existing variable |
createSignal<T>(key, defaultValue, options?) |
WritableSignal<T> |
Create Angular signal |
GlobalVariable API:
interface GlobalVariable<T> {
readonly key: string;
readonly value$: Observable<T>;
get(): T;
set(value: T): Promise<void>;
update(fn: (current: T) => T): Promise<void>;
reset(): Promise<void>;
destroy(): void;
}Examples:
// Create reactive variable
theme = this.globalState.createVar('theme', 'light', {
storage: 'localStorage',
persist: true,
ttl: 86400000 // 24 hours
});
// Use in template with async pipe
theme$ = this.theme.value$;
// Update value
await this.theme.set('dark');
// Update with function
await this.theme.update(t => t === 'light' ? 'dark' : 'light');
// Angular 16+ Signals
darkMode = this.globalState.createSignal('darkMode', false, {
storage: 'localStorage',
persist: true
});
// Use signal in template
// {{ darkMode() }}
// Update signal
this.darkMode.set(true);
this.darkMode.update(val => !val);Options:
interface GlobalVarOptions {
storage?: 'localStorage' | 'sessionStorage'; // Storage type
persist?: boolean; // Save to storage (default: true)
ttl?: number; // Time-to-live
encrypt?: boolean; // Encrypt value
}Unified service for dynamic storage selection and fallback support.
import { StorageManagerService } from 'ngx-webstore';
constructor(private storage: StorageManagerService) {}Direct Access:
// Access specific storage directly
await this.storage.local.set('key', value);
await this.storage.session.set('key', value);
await this.storage.cookie.set('key', value);
await this.storage.indexedDB.set('key', value);Dynamic Storage Selection:
// Specify storage at runtime
await this.storage.set('key', value, {
storage: 'indexedDB',
ttl: 3600000
});
const data = await this.storage.get('key', { storage: 'indexedDB' });Fallback Methods:
// Try each storage in fallback order
await this.storage.setWithFallback('important', value);
const data = await this.storage.getWithFallback<Data>('important');
// Remove from all storages
await this.storage.removeFromAll('key');
// Check available storages
const available = this.storage.getAvailableStorages();
// Returns: ['localStorage', 'sessionStorage', 'cookie', 'indexedDB']
const isAvailable = this.storage.isStorageAvailable('indexedDB');NgxWebStoreModule.forRoot({
// Default storage type
defaultStorage: 'localStorage',
// Global namespace prefix
namespace: 'myApp',
// Default TTL for all items (milliseconds)
defaultTTL: 86400000, // 24 hours
// Storage fallback order
fallbackOrder: ['localStorage', 'indexedDB', 'sessionStorage', 'cookie'],
// Encryption configuration
encryption: {
enabled: true,
secret: environment.storageSecret,
keyDerivationIterations: 100000
},
// IndexedDB configuration
indexedDB: {
dbName: 'my-app-db',
storeName: 'storage',
version: 1
},
// Cookie defaults
cookie: {
path: '/',
secure: true,
sameSite: 'Lax'
}
})interface StorageOptions {
ttl?: number; // Time-to-live in milliseconds
encrypt?: boolean; // Override global encryption setting
}
interface UnifiedStorageOptions extends StorageOptions {
storage?: 'localStorage' | 'sessionStorage' | 'cookie' | 'indexedDB';
}@Injectable({ providedIn: 'root' })
export class AuthService {
constructor(
private localStorage: LocalStorageService,
private cookieService: CookieService
) {}
async login(credentials: Credentials) {
const response = await this.api.login(credentials);
// Store access token in cookie (httpOnly would be better on server)
await this.cookieService.set('accessToken', response.accessToken, {
ttl: 900000, // 15 minutes
secure: true,
sameSite: 'Strict',
encrypt: true
});
// Store refresh token in localStorage
await this.localStorage.set('refreshToken', response.refreshToken, {
ttl: 2592000000, // 30 days
encrypt: true
});
// Store user info
await this.localStorage.set('user', response.user);
}
async logout() {
await this.cookieService.remove('accessToken');
await this.localStorage.remove('refreshToken');
await this.localStorage.remove('user');
}
watchUser(): Observable<User | null> {
return this.localStorage.watch<User>('user');
}
}@Component({
selector: 'app-settings',
template: `
<div>
<label>
<input type="checkbox" [checked]="darkMode()"
(change)="toggleDarkMode()">
Dark Mode
</label>
<select [value]="language()" (change)="setLanguage($event)">
<option value="en">English</option>
<option value="es">Spanish</option>
<option value="fr">French</option>
</select>
</div>
`
})
export class SettingsComponent {
// Using signals (Angular 16+)
darkMode = this.globalState.createSignal('darkMode', false, {
storage: 'localStorage',
persist: true
});
language = this.globalState.createSignal('language', 'en', {
storage: 'localStorage',
persist: true
});
constructor(private globalState: GlobalStateService) {}
toggleDarkMode() {
this.darkMode.update(val => !val);
}
setLanguage(event: Event) {
const value = (event.target as HTMLSelectElement).value;
this.language.set(value);
}
}@Injectable({ providedIn: 'root' })
export class DataService {
constructor(
private indexedDB: IndexedDBService,
private http: HttpClient
) {}
async getProducts(): Promise<Product[]> {
// Check cache first
const cached = await this.indexedDB.get<Product[]>('products');
if (cached) {
return cached;
}
// Fetch from API
const products = await firstValueFrom(this.http.get<Product[]>('/api/products'));
// Cache for 1 hour
await this.indexedDB.set('products', products, { ttl: 3600000 });
return products;
}
async invalidateCache() {
await this.indexedDB.remove('products');
}
}@Component({
selector: 'app-form',
template: `...`
})
export class FormComponent implements OnInit, OnDestroy {
form = this.fb.group({
name: [''],
email: [''],
message: ['']
});
constructor(
private fb: FormBuilder,
private sessionStorage: SessionStorageService
) {}
async ngOnInit() {
// Restore form state
const saved = await this.sessionStorage.get<any>('formDraft');
if (saved) {
this.form.patchValue(saved);
}
// Auto-save on changes
this.form.valueChanges.pipe(
debounceTime(500)
).subscribe(async value => {
await this.sessionStorage.set('formDraft', value);
});
}
async onSubmit() {
// Clear draft after submission
await this.sessionStorage.remove('formDraft');
}
}Encryption uses AES-GCM (256-bit) via the Web Crypto API with PBKDF2 key derivation.
// Enable globally
NgxWebStoreModule.forRoot({
encryption: {
enabled: true,
secret: 'your-secret-key',
keyDerivationIterations: 100000
}
})
// Or per-item
await this.localStorage.set('sensitive', data, { encrypt: true });Security Notes:
- Salt is generated per session and stored in sessionStorage
- Each encrypted value has its own initialization vector (IV)
- Never hardcode secrets in production code
- Use environment variables or secure key management
localStorage automatically syncs across tabs:
// Tab 1
await this.localStorage.set('counter', 5);
// Tab 2 - automatically receives update
this.localStorage.watch<number>('counter').subscribe(value => {
console.log('Counter updated:', value); // 5
});// Item expires after 1 hour
await this.localStorage.set('temp', data, { ttl: 3600000 });
// Check if expired
const data = await this.localStorage.get('temp'); // null if expiredPrevent key collisions between different apps or modules:
NgxWebStoreModule.forRoot({
namespace: 'myApp'
})
// Keys are prefixed: "myApp:user", "myApp:settings"
await this.localStorage.set('user', data);interface User {
id: number;
name: string;
email: string;
}
// Type-safe get
const user = await this.localStorage.get<User>('user');
// user is typed as User | null
// Type-safe watch
this.localStorage.watch<User>('user').subscribe(user => {
// user is typed as User | null
});- Chrome/Edge: ✅ Full support
- Firefox: ✅ Full support
- Safari: ✅ Full support
- IE11: ❌ Not supported (requires modern browser APIs)
Required APIs:
- Web Storage API (localStorage, sessionStorage)
- IndexedDB API
- Web Crypto API (for encryption)
import { LocalStorageService } from 'ngx-webstore';
try {
await this.localStorage.set('key', largeData);
} catch (error) {
if (error.message.includes('quota exceeded')) {
// Handle storage quota exceeded
console.error('Storage quota exceeded');
}
}
// Check availability first
if (this.localStorage.isAvailable) {
await this.localStorage.set('key', data);
}-
Use appropriate storage types
localStorage- User preferences, app statesessionStorage- Temporary data, form draftscookies- Auth tokens (with httpOnly on server)indexedDB- Large datasets, offline data
-
Set reasonable TTLs
// Sensitive data - short TTL await storage.set('token', token, { ttl: 900000 }); // 15 min // User preferences - long TTL await storage.set('theme', theme, { ttl: 2592000000 }); // 30 days
-
Encrypt sensitive data
await storage.set('ssn', data, { encrypt: true });
-
Use reactive patterns
// Instead of polling theme$ = this.localStorage.watch<string>('theme');
-
Namespace your app
NgxWebStoreModule.forRoot({ namespace: 'myApp' })
- localStorage/sessionStorage: Synchronous operations wrapped in Promises
- IndexedDB: Fully asynchronous, best for large data
- Cookies: Limited to ~4KB, sent with every HTTP request
- Serialization: Automatic for complex types (Date, Map, Set)
if (!this.localStorage.isAvailable) {
console.warn('localStorage not available, using fallback');
// Use alternative storage
}try {
await this.localStorage.set('key', data);
} catch (error) {
// Clear old data or use IndexedDB
await this.localStorage.clear();
}// Make sure encryption is initialized
NgxWebStoreModule.forRoot({
encryption: {
enabled: true,
secret: 'your-secret' // Required!
}
})MIT
For issues and feature requests, please use the GitHub issue tracker.