Skip to content

Instantly share code, notes, and snippets.

@ccarrasc
Last active April 4, 2023 16:57
Show Gist options
  • Save ccarrasc/fbf71c662a32775f9b93d85481b8d59d to your computer and use it in GitHub Desktop.
Save ccarrasc/fbf71c662a32775f9b93d85481b8d59d to your computer and use it in GitHub Desktop.
Wrapping storage access with BehaviorSubject for Angular
import { Injectable } from '@angular/core';
import StorageService from './storage.service';
@Injectable()
export class LocalStorageService extends StorageService {
private _storage = localStorage;
constructor() {
super();
}
protected get storage(): Storage {
return this._storage;
}
}
import { Injectable } from '@angular/core';
import StorageService from './storage.service';
@Injectable()
export class SessionStorageService extends StorageService {
private _storage = sessionStorage;
constructor() {
super();
}
protected get storage(): Storage {
return this._storage;
}
}
import { Observable, BehaviorSubject } from 'rxjs/Rx';
abstract class StorageService {
private itemSources: Map<string, BehaviorSubject<string>> = new Map();
protected abstract get storage(): Storage;
constructor() {
addEventListener('storage', (event: StorageEvent) => {
if (event.key) {
if (this.itemSources.has(event.key)) {
this.itemSources.get(event.key).next(event.newValue);
}
}
});
}
getItem(key: string): Observable<string> {
if (!this.itemSources.has(key)) {
this.itemSources.set(key, new BehaviorSubject<string>(this.storage.getItem(key)));
}
return this.itemSources.get(key).asObservable();
}
setItem(key: string, value: string) {
try {
this.storage.setItem(key, value);
if(this.itemSources.has(key)) {
this.itemSources.get(key).next(this.storage.getItem(key));
}
}
catch (error) {
this.itemSources.get(key).error(error);
}
}
removeItem(key: string) {
this.storage.removeItem(key);
if (this.itemSources.has(key)) {
this.itemSources.get(key).next(this.storage.getItem(key)); // Expect to be null
this.itemSources.delete(key);
}
}
clear() {
this.storage.clear();
this.itemSources.forEach((itemSource: BehaviorSubject<string>) => {
itemSource.next(null);
itemSource.complete();
});
this.itemSources.clear();
}
}
export default StorageService;
@ccarrasc
Copy link
Author

ccarrasc commented Apr 4, 2023

In your eventListener 'storage', shouldn't you also check this ? : event.storageArea === this.storage

I don't think so since the event is typed to StorageEvent: https://developer.mozilla.org/en-US/docs/Web/API/StorageEvent

@VincentQueignec
Copy link

Your StorageEvent can be fired from local and session from my understanding ? Am I wrong ?
If yes, if you have the same key from your local and session storage, you might override the wrong storage in addition of the good one.

@ccarrasc
Copy link
Author

ccarrasc commented Apr 4, 2023

@VincentQueignec I don't think so, but I do not have any unit tests to validate that and this is also a very old snippet.
From what I see, StorageService is abstract, and the concrete implementations specify the storage type. I don't see how you can cross into the wrong storage when using a concrete type (see the getter for storage).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment