Skip to content

saurabh-vaish/ngx-webstore

Repository files navigation

ngx-webstore

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:

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

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:

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
}

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.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors