ngx-webstore

February 7, 2026 ยท View on GitHub

A comprehensive Angular library for browser storage management with TypeScript support, reactive APIs, encryption, TTL, and more.

Features

  • ๐Ÿ” 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

Installation

npm install ngx-webstore

Quick Start

// 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);
  }
}

API Documentation

Services

LocalStorageService

Injectable service for browser localStorage with cross-tab synchronization.

import { LocalStorageService } from 'ngx-webstore';

constructor(private localStorage: LocalStorageService) {}

Methods:

MethodReturn TypeDescription
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();

SessionStorageService

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 });

CookieService

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.


IndexedDBService

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
  }
})

GlobalStateService

Injectable service for reactive global application state with optional persistence.

import { GlobalStateService } from 'ngx-webstore';

constructor(private globalState: GlobalStateService) {}

Methods:

MethodReturn TypeDescription
createVar<T>(key, defaultValue, options?)GlobalVariable<T>Create reactive variable
getVar<T>(key)GlobalVariable<T> | undefinedGet 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
}

StorageManagerService

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');

Configuration

Module Configuration

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'
  }
})

Storage Options

interface StorageOptions {
  ttl?: number;         // Time-to-live in milliseconds
  encrypt?: boolean;    // Override global encryption setting
}

interface UnifiedStorageOptions extends StorageOptions {
  storage?: 'localStorage' | 'sessionStorage' | 'cookie' | 'indexedDB';
}

Usage Examples

User Authentication

@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');
  }
}

Application Settings

@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);
  }
}

Caching API Responses

@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');
  }
}

Form State Persistence

@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');
  }
}

Advanced Features

Encryption

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

Cross-Tab Synchronization

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
});

TTL and Expiration

// 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 expired

Namespacing

Prevent key collisions between different apps or modules:

NgxWebStoreModule.forRoot({
  namespace: 'myApp'
})

// Keys are prefixed: "myApp:user", "myApp:settings"
await this.localStorage.set('user', data);

Type Safety

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
});

Browser Compatibility

  • 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)

Error Handling

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);
}

Best Practices

  1. Use appropriate storage types

    • localStorage - User preferences, app state
    • sessionStorage - Temporary data, form drafts
    • cookies - Auth tokens (with httpOnly on server)
    • indexedDB - Large datasets, offline data
  2. 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
    
  3. Encrypt sensitive data

    await storage.set('ssn', data, { encrypt: true });
    
  4. Use reactive patterns

    // Instead of polling
    theme$ = this.localStorage.watch<string>('theme');
    
  5. Namespace your app

    NgxWebStoreModule.forRoot({
      namespace: 'myApp'
    })
    

Performance Considerations

  • 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)

Troubleshooting

Storage not available

if (!this.localStorage.isAvailable) {
  console.warn('localStorage not available, using fallback');
  // Use alternative storage
}

Quota exceeded

try {
  await this.localStorage.set('key', data);
} catch (error) {
  // Clear old data or use IndexedDB
  await this.localStorage.clear();
}

Encryption not working

// Make sure encryption is initialized
NgxWebStoreModule.forRoot({
  encryption: {
    enabled: true,
    secret: 'your-secret' // Required!
  }
})

License

MIT

Support

For issues and feature requests, please use the GitHub issue tracker.