I store data in both browser's Local and Session storage. What is a good design practice for implementing Local and Session Storage services? I have a generic service for handling json.
@Injectable()
export class StorageService {
private storage: any;
constructor() {
this.storage = sessionStorage;
}
public retrieve(key: string): any {
const item = this.storage.getItem(key);
if (item && item !== 'undefined') {
return JSON.parse(this.storage.getItem(key));
}
return;
}
public store(key: string, value: any) {
this.storage.setItem(key, JSON.stringify(value));
}
public remove(key: string) {
this.storage.removeItem(key);
}
}
As you can see, currently its working with Session. I need to handle also Local.
public removeLocal() { .. }
public removeSession() { .. }
private remove(key: string, storage: Storage) {
storage.removeItem(key);
}
Expanding the answer of @StepUp after trial and error research. https://stackoverflow.com/a/73643938/9442199
Use case: I want Local Storage Service in one component.ts
and in another my-service.ts
the Sesssion Storage Service.
Also, rename export interface Storage to IStorage. Because there is confusion between library and our own.
Please check this Stackblitz example to see the concrete implementation.
service or component.ts
constructor() {
const storageFactory = new StorageFactory();
const storage = new StorageService(storageFactory.getInstanceByKey('local'))
storage.store('some key', 'some value')
}
I have to instantiate these classes in all the constructors where I need a browser storage. This should not be the case since Angular has DI which can give me only one instance. If I have 10 components I need to write those lines in each of them.
We will start from the bottom.
So, all web tutorials use an obsolete version of private injector: Injector
without InjectionTokens. I have found a way.
This is how Strategy Pattern is implemented in Angular.
shared.module.ts
{
provide: LOCAL, // the injection token (~string identifier)
useClass: LocalStorage
},
{
provide: SESSION,
useClass: SessionStorage
},
storage-token.ts
// Tokens work as an abstract factory provider. Specific services linked to a string key in SharedModule.
export const LOCAL = new InjectionToken<string>('LOCAL');
export const SESSION = new InjectionToken<string>('SESSION');
your-class.service.ts
constructor(
@Inject(LOCAL) private localStorageService: IStorage, //The IStrategy
OR
@Inject(SESSION) private sessionStorageService: IStorage,
)
Where needed, import SharedModule
and in the component/service import storage-token.ts
and istorage.ts
.
Maybe we want to implement some customization before giving the LocalStorage class. The factory is created by the providers:[] of Angular with token identifiers. See factory in @StepUp answer.
shared.module.ts
{
provide: LOCAL,
useFactory: () => {
return new StorageFactory().getInstanceByKey('local');
}
},
{
provide: SESSION,
useFactory: () => {
return new StorageFactory().getInstanceByKey('session');
}
},
The next problem is that we have duplicate code in local and session ~ some json stringify upon retrieve and set item. Using @StepUp's service.
shared.module.ts
{
provide: LOCAL,
useFactory: () => {
return new StorageService(new StorageFactory().getInstanceByKey('local'));
}
},
{
provide: SESSION,
useFactory: () => {
return new StorageService(new StorageFactory().getInstanceByKey('session'));
}
},
@Inject(LOCAL) private localStorageService: StorageService,
@Inject(SESSION) private sessionStorageService: StorageService,
Instead of new StorageService(IStorage)
you can use Template Design Pattern and have them inherit
from an abstract class: LocalStorage extends StorageService where you put the repetitive code.
StorageService.ts
abstract getItem();
retrieve() {
const res = this.getItem();
// json stringify or additional manipulation
return res;
}
And return to having only: module.ts
useFactory: () => { return new StorageFactory().getInstanceByKey('local'); }
This guide is offering a very well structured solution for Strategy Pattern: Local, Session, Cookies storage. But he is choosing which service at the module level. If you have two components in the same module how do you choose Local for one and Session for the other? I don't see the use of this method. I paste it, because the classes are very nicely packed and respect a good design.