Skip to content

A modern, reactive Angular service for browser storage management with AES-GCM encryption, TTL, change notifications, and Apollo-style providers

Notifications You must be signed in to change notification settings

edisonaugusthy/ng-storage

Repository files navigation

ngx-browser-storage

npm versionLicense: MITAngularTypeScript

🚀 A modern, reactive Angular storage with AES-GCM encryption, TTL support, change notifications, and signal-based reactivity.

✨ Features

  • 🔄 Reactive State Management - Built with Angular signals and RxJS
  • 🔐 AES-GCM Encryption - Encryption with Web Crypto API
  • TTL Support - Automatic data expiration
  • 📡 Change Notifications - Real-time storage change watching
  • 🏪 Multi-Storage Support - localStorage and sessionStorage
  • 🎯 Multiple Instances - Named storage configurations
  • 📊 Storage Analytics - Usage statistics and monitoring
  • 🛡️ Type Safe - Full TypeScript support with generics
  • 🌐 Cross-Browser - Graceful fallbacks for older browsers

📦 Installation

npm install ngx-browser-storage

🔢 Versioning

This library follows Angular's versioning for clear compatibility:

Angular Versionngx-browser-storage VersionStatus
20.0.x20.0.x✅ Current
20.1.x20.1.x🔄 Planned

🚀 Quick Start

Basic Setup

// app.config.tsimport{ApplicationConfig}from"@angular/core";import{provideNgxBrowserStorage}from"ngx-browser-storage";exportconstappConfig: ApplicationConfig={providers: [provideNgxBrowserStorage({prefix: "myapp",storageType: "localStorage",defaultTTL: 60,// 1 hourenableLogging: false,}),],};

Component Usage

import{Component,inject,signal}from"@angular/core";import{NgxBrowserStorageService}from"ngx-browser-storage"; @Component({selector: "app-user-profile",template: ` <div> <input [(ngModel)]="username" placeholder="Username" /> <button (click)="saveUser()">Save</button> <button (click)="saveUserSecure()">Save Encrypted</button> @if (currentUser(); as user){ <p>Welcome,{{user.name }}!</p> } <p>Items in storage:{{storage.stats().itemCount }}</p> </div> `,})exportclassUserProfileComponent{privatestorage=inject(NgxBrowserStorageService);username="";currentUser=signal<User|null>(null);asyncngOnInit(){// Create reactive signal for user datathis.currentUser=awaitthis.storage.createSignal<User>("currentUser");}asyncsaveUser(){constuser={name: this.username,id: Date.now()};awaitthis.storage.setData("currentUser",user);}asyncsaveUserSecure(){constuser={name: this.username,id: Date.now()};awaitthis.storage.setData("currentUser",user,{encrypt: true,ttlMinutes: 60,});}}

🔐 Security Features

AES-GCM Encryption

NgxBrowserStorage provides encryption using the Web Crypto API:

// Store sensitive data with encryptionawaitstorage.setData("apiToken","secret-key-123",{encrypt: true,ttlMinutes: 120,// Expires in 2 hours});// Retrieve encrypted dataconsttoken=awaitstorage.getData("apiToken",{decrypt: true,defaultValue: null,});// Check encryption supportif(storage.isEncryptionSupported()){console.log("Using AES-GCM encryption");}else{console.log("Falling back to Base64 encoding");}

Security Comparison

FeatureBase64 FallbackAES-GCM Encryption
Key LengthNone256-bit
Data Integrity✅ Authenticated
Tamper Protection✅ Auth Tags
Browser SupportUniversalModern + Fallback

⚙️ Configuration

Simple Configuration

import{provideNgxBrowserStorage}from"ngx-browser-storage";exportconstappConfig: ApplicationConfig={providers: [provideNgxBrowserStorage({prefix: "myapp",storageType: "localStorage",defaultTTL: 0,// No expirationenableLogging: false,caseSensitive: false,}),],};

Advanced Configuration with Factories

import{provideNgxBrowserStorage}from"ngx-browser-storage";import{environment}from"./environments/environment";exportconstappConfig: ApplicationConfig={providers: [provideNgxBrowserStorage(()=>({prefix: environment.production ? "prod-app" : "dev-app",storageType: environment.production ? "localStorage" : "sessionStorage",defaultTTL: environment.production ? 60 : 30,enableLogging: !environment.production,caseSensitive: false,}),{autoCleanup: true,strictMode: environment.production,enableMetrics: !environment.production,}),],};

Multiple Named Storage Instances

import{provideNamedNgxBrowserStorage,NgxBrowserStorageManager}from"ngx-browser-storage";exportconstappConfig: ApplicationConfig={providers: [provideNamedNgxBrowserStorage(()=>({user: {prefix: "user-data",storageType: "localStorage",defaultTTL: 0,// PersistentenableLogging: false,},cache: {prefix: "app-cache",storageType: "localStorage",defaultTTL: 60,// 1 hourenableLogging: false,},session: {prefix: "session-data",storageType: "sessionStorage",defaultTTL: 30,// 30 minutesenableLogging: true,},})),NgxBrowserStorageManager,],};

📚 API Reference

Core Methods

setData<T>(key: string, value: T, options?): Promise<boolean>

Store data with optional encryption and TTL.

// Basic storageawaitstorage.setData("user",{name: "John",age: 30});// With encryption and TTLawaitstorage.setData("session",userData,{encrypt: true,ttlMinutes: 60,});

getData<T>(key: string, options?): Promise<T | null>

Retrieve data with optional decryption.

// Basic retrievalconstuser=awaitstorage.getData<User>("user");// With decryption and default valueconsttheme=awaitstorage.getData("theme",{decrypt: true,defaultValue: "light",});

hasKey(key: string): Promise<boolean>

Check if a key exists in storage.

constuserExists=awaitstorage.hasKey("currentUser");

removeData(key: string): boolean

Remove a specific key from storage.

storage.removeData("temporaryData");

removeAll(): boolean

Clear all storage data with the current prefix.

storage.removeAll();

Reactive Features

createSignal<T>(key: string, defaultValue?): Promise<Signal<T | null>>

Create a reactive signal that automatically updates when storage changes.

// Create reactive signalsconstuserSignal=awaitstorage.createSignal<User>("currentUser");constthemeSignal=awaitstorage.createSignal("theme","light");// Use in template @Component({template: `<p>Hello{{userSignal()?.name }}!</p>`,})exportclassMyComponent{userSignal=signal<User|null>(null);asyncngOnInit(){this.userSignal=awaitinject(NgxBrowserStorageService).createSignal<User>("user");}}

watch<T>(key: string): Observable<T | null>

Watch for changes to a specific key.

// Watch single keystorage.watch<string>("theme").subscribe((theme)=>{document.body.className=theme||"light";});

watchAll(): Observable<StorageChangeEvent>

Watch for all storage changes.

storage.watchAll().subscribe((event)=>{console.log(`${event.action} on ${event.key}:`,event.newValue);});

watchKeys<T>(keys: string[]): Observable<{key: string, value: T}>

Watch multiple specific keys.

storage.watchKeys(["user","settings","theme"]).subscribe(({ key, value })=>{console.log(`${key} changed:`,value);});

watchPattern<T>(pattern: string): Observable<{key: string, value: T}>

Watch keys matching a pattern.

// Watch all user-related keysstorage.watchPattern("user.*").subscribe(({ key, value })=>{console.log(`User data ${key} changed:`,value);});

Advanced Methods

updateData<T>(key, updateFn, options?): Promise<boolean>

Update existing data using a function.

awaitstorage.updateData("cart",(current: CartItem[]=[])=>[...current,newItem],{encrypt: true});

setIfNotExists<T>(key, value, options?): Promise<boolean>

Set data only if key doesn't exist.

constwasSet=awaitstorage.setIfNotExists("config",defaultConfig);

getStorageStats(): Promise<StorageStats>

Get detailed storage statistics.

conststats=awaitstorage.getStorageStats();console.log(`Total: ${stats.totalItems} items, ${stats.totalSize} bytes`);

Security Methods

isEncryptionSupported(): boolean

Check if AES-GCM encryption is available.

if(storage.isEncryptionSupported()){// Use full encryption}else{// Handle fallback}

clearEncryptionKey(): void

Clear cached encryption key (useful for logout).

// Clear encryption key for securitystorage.clearEncryptionKey();

💼 Real-World Examples

User Authentication Service

@Injectable({providedIn: "root"})exportclassAuthService{privatestorage=inject(NgxBrowserStorageService);// Reactive authentication stateisAuthenticated=computed(()=>this.storage.stats().keys.includes("auth"));currentUser=signal<User|null>(null);asyncngOnInit(){this.currentUser=awaitthis.storage.createSignal<User>("currentUser");}asynclogin(credentials: LoginCredentials): Promise<void>{constresult=awaitthis.authApi.login(credentials);// Store encrypted auth token with 8-hour TTLawaitthis.storage.setData("auth",result.token,{encrypt: true,ttlMinutes: 8*60,});awaitthis.storage.setData("currentUser",result.user);}asynclogout(): Promise<void>{this.storage.removeMultiple(["auth","currentUser"]);this.storage.clearEncryptionKey();}}

Shopping Cart Service

@Injectable({providedIn: "root"})exportclassCartService{privatestorage=inject(NgxBrowserStorageService);// Reactive cart stateitems=signal<CartItem[]>([]);itemCount=computed(()=>this.items().reduce((sum,item)=>sum+item.quantity,0));total=computed(()=>this.items().reduce((sum,item)=>sum+item.price*item.quantity,0));asyncngOnInit(){this.items=awaitthis.storage.createSignal<CartItem[]>("cart",[]);}asyncaddItem(product: Product,quantity=1): Promise<void>{awaitthis.storage.updateData("cart",(current: CartItem[]=[])=>{constexisting=current.find((item)=>item.id===product.id);if(existing){existing.quantity+=quantity;return[...current];}return[...current,{ ...product, quantity }];},{encrypt: true,ttlMinutes: 60});}asyncremoveItem(productId: string): Promise<void>{awaitthis.storage.updateData("cart",(current: CartItem[]=[])=>current.filter((item)=>item.id!==productId),{encrypt: true});}clearCart(): void{this.storage.removeData("cart");}}

User Preferences Service

@Injectable({providedIn: "root"})exportclassPreferencesService{privatestorage=inject(NgxBrowserStorageService);// Reactive preferencestheme=signal<"light"|"dark">("light");language=signal<string>("en");notifications=signal<boolean>(true);asyncngOnInit(){// Initialize reactive preferencesthis.theme=awaitthis.storage.createSignal("theme","light");this.language=awaitthis.storage.createSignal("language","en");this.notifications=awaitthis.storage.createSignal("notifications",true);// Auto-apply theme changeseffect(()=>{document.body.setAttribute("data-theme",this.theme());});}asyncupdatePreference<T>(key: string,value: T): Promise<void>{awaitthis.storage.setData(key,value,{encrypt: true});}asyncresetToDefaults(): Promise<void>{awaitthis.updatePreference("theme","light");awaitthis.updatePreference("language","en");awaitthis.updatePreference("notifications",true);}}

Form Auto-Save Service

@Injectable({providedIn: "root"})exportclassFormAutoSaveService{privatestorage=inject(NgxBrowserStorageService);privatesaveTimeouts=newMap<string,number>();asyncautoSave<T>(formId: string,data: T,delayMs=1000): Promise<void>{// Debounce savesconstexistingTimeout=this.saveTimeouts.get(formId);if(existingTimeout){clearTimeout(existingTimeout);}consttimeoutId=setTimeout(async()=>{awaitthis.storage.setData(`form_${formId}`,{ data,savedAt: Date.now(),},{encrypt: true,ttlMinutes: 60,});this.saveTimeouts.delete(formId);},delayMs);this.saveTimeouts.set(formId,timeoutId);}asyncgetSavedData<T>(formId: string): Promise<{data: T;savedAt: number}|null>{returnawaitthis.storage.getData(`form_${formId}`,{decrypt: true});}clearSavedData(formId: string): void{consttimeoutId=this.saveTimeouts.get(formId);if(timeoutId){clearTimeout(timeoutId);this.saveTimeouts.delete(formId);}this.storage.removeData(`form_${formId}`);}}

Multiple Storage Instances

@Component({selector: "app-dashboard",})exportclassDashboardComponent{privatestorageManager=inject(NgxBrowserStorageManager);// Different storage instances for different purposesprivateuserStorage=this.storageManager.getStorage("user");privatecacheStorage=this.storageManager.getStorage("cache");privatesessionStorage=this.storageManager.getStorage("session");asyncngOnInit(){// Load persistent user dataconstuserProfile=awaitthis.userStorage.getData("profile");// Load cached application dataconstcachedData=awaitthis.cacheStorage.getData("dashboard-metrics");// Save current session stateawaitthis.sessionStorage.setData("current-view","dashboard",{encrypt: true,ttlMinutes: 30,});}}

🧪 Testing

Test Configuration

import{provideNgxBrowserStorage}from"ngx-browser-storage";// test-setup.tsexportfunctionprovideStorageForTesting(){returnprovideNgxBrowserStorage({prefix: "test",storageType: "sessionStorage",defaultTTL: 0,enableLogging: true,caseSensitive: false,},{autoCleanup: false,strictMode: true,enableMetrics: false,});}// In test filesdescribe("NgxBrowserStorageService",()=>{letservice: NgxBrowserStorageService;beforeEach(()=>{TestBed.configureTestingModule({providers: [...provideStorageForTesting()],});service=TestBed.inject(NgxBrowserStorageService);});it("should store and retrieve data",async()=>{awaitservice.setData("test","value");constresult=awaitservice.getData("test");expect(result).toBe("value");});it("should handle encryption",async()=>{awaitservice.setData("secret","encrypted-data",{encrypt: true});constresult=awaitservice.getData("secret",{decrypt: true});expect(result).toBe("encrypted-data");});it("should create reactive signals",async()=>{constsignal=awaitservice.createSignal<string>("reactive-test","default");expect(signal()).toBe("default");awaitservice.setData("reactive-test","updated");expect(signal()).toBe("updated");});});

🔄 Migration Guide

From ng7-storage to ngx-browser-storage

This is a breaking change that requires updates:

1. Package Installation

# Remove old package npm uninstall ng7-storage # Install new package npm install ngx-browser-storage

2. Update Imports

// Beforeimport{provideNgStorageConfig}from"ng7-storage";// Afterimport{provideNgxBrowserStorage}from"ngx-browser-storage";

3. Update Method Calls

// Before (synchronous)constuser=storage.getData("user");storage.setData("user",newUser);// After (asynchronous)constuser=awaitstorage.getData("user");awaitstorage.setData("user",newUser);

4. Update Reactive Features

// Beforeconstsignal=storage.createSignal("user");// Afterconstsignal=awaitstorage.createSignal("user");

5. Storage Prefix Changes

The default prefix changed from ng-storage to ngx-browser-storage. Existing data with the old prefix won't be automatically migrated. You can:

  • Keep using a custom prefix: prefix: 'ng-storage'
  • Or migrate data manually in your application

Breaking Changes Summary

  • ⚠️Package Name: ng7-storagengx-browser-storage
  • ⚠️Method Signatures: Core methods now async
  • ⚠️Signal Creation: Now async
  • ⚠️Default Prefix: ng-storagengx-browser-storage
  • Enhanced: Much stronger AES-GCM encryption
  • Compatible: Existing stored data still readable

🌐 Browser Compatibility

BrowserVersionStorage SupportAES-GCM Encryption
Chrome37+
Firefox34+
Safari7+
Edge12+
IE11❌ (Base64 fallback)
IE8-10❌ (Base64 fallback)

🤝 Contributing

We welcome contributions! Please see our Contributing Guide for details.

Development Setup

# Clone repository git clone https://github.com/edisonaugusthy/ng-storage.git # Install dependencies npm install # Run tests npm test# Build library npm run build # Check Angular version alignment npm run check-angular-version

📝 Changelog

v20.0.3 - Major Rebranding & Enhancement

🚨 Breaking Changes

  • Package renamed: ng7-storagengx-browser-storage
  • Methods now async: setData(), getData(), hasKey() for encryption support
  • Signal creation async: createSignal() now returns Promise<Signal<T>>
  • Default prefix changed: ng-storagengx-browser-storage

✨ New Features

  • AES-GCM Encryption: Military-grade security with Web Crypto API
  • PBKDF2 Key Derivation: 100,000 iterations for secure key generation
  • Version Alignment: Now follows Angular version numbers (20.0.3)
  • Enhanced Security: Data integrity protection with authentication tags
  • Better Fallbacks: Automatic Base64 fallback for older browsers
  • Improved TypeScript: Better type safety and generics

🛡️ Security Enhancements

  • Unique Encryption: Random IVs ensure unique encryption per data item
  • Tamper Protection: Authentication tags prevent data modification
  • Key Management: Secure key caching and rotation support
  • Browser Detection: Automatic encryption capability detection

🔧 Improvements

  • Performance: Optimized key caching and encryption operations
  • Error Handling: Better error messages and fallback strategies
  • Documentation: Comprehensive guides and examples
  • Testing: Enhanced test coverage and utilities

Legacy Versions

  • v19.0.0: Previous version with basic Base64 encoding
  • v1.0.0: Initial release

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

🙏 Acknowledgments

  • Angular team for the amazing framework and signals
  • Web Crypto API for enabling secure client-side encryption
  • RxJS team for reactive programming utilities
  • Community contributors and users

📞 Support


Made with ❤️ for the Angular community

⭐ Star this repo | 🍴 Fork it | 📋 Report Issues

About

A modern, reactive Angular service for browser storage management with AES-GCM encryption, TTL, change notifications, and Apollo-style providers

Topics

Resources

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •