🚀 A modern, reactive Angular storage with AES-GCM encryption, TTL support, change notifications, and signal-based reactivity.
- 🔄 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
npm install ngx-browser-storageThis library follows Angular's versioning for clear compatibility:
| Angular Version | ngx-browser-storage Version | Status |
|---|---|---|
| 20.0.x | 20.0.x | ✅ Current |
| 20.1.x | 20.1.x | 🔄 Planned |
// 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,}),],};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,});}}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");}| Feature | Base64 Fallback | AES-GCM Encryption |
|---|---|---|
| Key Length | None | 256-bit |
| Data Integrity | ❌ | ✅ Authenticated |
| Tamper Protection | ❌ | ✅ Auth Tags |
| Browser Support | Universal | Modern + Fallback |
import{provideNgxBrowserStorage}from"ngx-browser-storage";exportconstappConfig: ApplicationConfig={providers: [provideNgxBrowserStorage({prefix: "myapp",storageType: "localStorage",defaultTTL: 0,// No expirationenableLogging: false,caseSensitive: false,}),],};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,}),],};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,],};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,});Retrieve data with optional decryption.
// Basic retrievalconstuser=awaitstorage.getData<User>("user");// With decryption and default valueconsttheme=awaitstorage.getData("theme",{decrypt: true,defaultValue: "light",});Check if a key exists in storage.
constuserExists=awaitstorage.hasKey("currentUser");Remove a specific key from storage.
storage.removeData("temporaryData");Clear all storage data with the current prefix.
storage.removeAll();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 for changes to a specific key.
// Watch single keystorage.watch<string>("theme").subscribe((theme)=>{document.body.className=theme||"light";});Watch for all storage changes.
storage.watchAll().subscribe((event)=>{console.log(`${event.action} on ${event.key}:`,event.newValue);});Watch multiple specific keys.
storage.watchKeys(["user","settings","theme"]).subscribe(({ key, value })=>{console.log(`${key} changed:`,value);});Watch keys matching a pattern.
// Watch all user-related keysstorage.watchPattern("user.*").subscribe(({ key, value })=>{console.log(`User data ${key} changed:`,value);});Update existing data using a function.
awaitstorage.updateData("cart",(current: CartItem[]=[])=>[...current,newItem],{encrypt: true});Set data only if key doesn't exist.
constwasSet=awaitstorage.setIfNotExists("config",defaultConfig);Get detailed storage statistics.
conststats=awaitstorage.getStorageStats();console.log(`Total: ${stats.totalItems} items, ${stats.totalSize} bytes`);Check if AES-GCM encryption is available.
if(storage.isEncryptionSupported()){// Use full encryption}else{// Handle fallback}Clear cached encryption key (useful for logout).
// Clear encryption key for securitystorage.clearEncryptionKey();@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();}}@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");}}@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);}}@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}`);}}@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,});}}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");});});This is a breaking change that requires updates:
# Remove old package npm uninstall ng7-storage # Install new package npm install ngx-browser-storage// Beforeimport{provideNgStorageConfig}from"ng7-storage";// Afterimport{provideNgxBrowserStorage}from"ngx-browser-storage";// Before (synchronous)constuser=storage.getData("user");storage.setData("user",newUser);// After (asynchronous)constuser=awaitstorage.getData("user");awaitstorage.setData("user",newUser);// Beforeconstsignal=storage.createSignal("user");// Afterconstsignal=awaitstorage.createSignal("user");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
⚠️ Package Name:ng7-storage→ngx-browser-storage⚠️ Method Signatures: Core methods now async⚠️ Signal Creation: Now async⚠️ Default Prefix:ng-storage→ngx-browser-storage- ✅ Enhanced: Much stronger AES-GCM encryption
- ✅ Compatible: Existing stored data still readable
| Browser | Version | Storage Support | AES-GCM Encryption |
|---|---|---|---|
| Chrome | 37+ | ✅ | ✅ |
| Firefox | 34+ | ✅ | ✅ |
| Safari | 7+ | ✅ | ✅ |
| Edge | 12+ | ✅ | ✅ |
| IE | 11 | ✅ | ❌ (Base64 fallback) |
| IE | 8-10 | ✅ | ❌ (Base64 fallback) |
We welcome contributions! Please see our Contributing Guide for details.
# 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- Package renamed:
ng7-storage→ngx-browser-storage - Methods now async:
setData(),getData(),hasKey()for encryption support - Signal creation async:
createSignal()now returnsPromise<Signal<T>> - Default prefix changed:
ng-storage→ngx-browser-storage
- 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
- 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
- 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
- v19.0.0: Previous version with basic Base64 encoding
- v1.0.0: Initial release
This project is licensed under the MIT License - see the LICENSE file for details.
- 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
- 🐛 Bug Reports: GitHub Issues
- 💬 Discussions: GitHub Discussions
- 🔐 Security Issues: Please report security vulnerabilities privately via email
Made with ❤️ for the Angular community