import { Injectable } from '@angular/core'; import { fromEvent, merge, Observable, Subject } from 'rxjs'; import { filter, map, startWith } from 'rxjs/operators'; export interface LocalStorageObject { name: string; value: string | null; } const deserializeValue = (value: string): T => { try { return JSON.parse(value); } catch { return value as unknown as T; } }; @Injectable({ providedIn: 'root', }) export class LocalStorageService { private _storage = window.localStorage; private _storageUpdateNotifier$ = new Subject(); storageUpdates$ = this._storageUpdateNotifier$.asObservable(); initLocalStorage(storageObjs: LocalStorageObject[]): void { if (this._isLocalStorageEnabled()) { storageObjs.forEach(({ name, value }) => { this._storage.setItem(name, value); }); } } setItem(key: string, value: string): void { this._storage.setItem(key, value); this._storageUpdateNotifier$.next({ name: key, value }); } getItem(key: string): any { const retrieved = this._storage.getItem(key); try { return deserializeValue(retrieved); } catch { return retrieved; } } removeItem(key: string): void { this._storage.removeItem(key); this._storageUpdateNotifier$.next({ name: key, value: null }); } clear(): void { this._storage.clear(); this._storageUpdateNotifier$.next(null); } getUpdatesForKey(key: string): Observable { return merge( fromEvent(window, 'storage').pipe( filter((event: StorageEvent) => event.key === key), map(data => deserializeValue(data.newValue)) ), this.storageUpdates$.pipe( filter(update => update.name === key), map(update => deserializeValue(update.value)) ) ).pipe(startWith(this.getItem(key) || false)); } private _isLocalStorageEnabled(): boolean { try { const key = `__storage__test`; this._storage.setItem(key, null); this._storage.removeItem(key); return true; } catch (e) { return false; } } }