Skip to content

Commit 1e1fe55

Browse files
committed
feat(lib): allow to add custom storage
1 parent 0ee5eff commit 1e1fe55

File tree

7 files changed

+102
-21
lines changed

7 files changed

+102
-21
lines changed

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,27 @@ formsManager.upsert(formName, abstractContorl, {
256256
});
257257
```
258258

259+
By default the library provides LocalStorageManager and SessionStorageManager. It's possible to store the form value into a custom storage. Just implement the PersistManager interface, and use it when calling the upsert function.
260+
261+
```ts
262+
export class StateStoreManager<T> implements PersistManager<T> {
263+
setValue(key: string, data: T) {
264+
...
265+
}
266+
267+
getValue(key: string) {
268+
...
269+
}
270+
}
271+
```
272+
273+
```ts
274+
formsManager.upsert(formName, abstractContorl, {
275+
persistState: true,
276+
persistManager: new StateStoreManager(),
277+
});
278+
```
279+
259280
## Validators
260281

261282
The library exposes two helpers method for adding cross component validation:

projects/ngneat/forms-manager/src/lib/forms-manager.ts

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { Inject, Injectable, Optional } from '@angular/core';
2-
import { AbstractControl, FormArray, FormGroup } from '@angular/forms';
2+
import { AbstractControl, FormGroup, FormArray } from '@angular/forms';
3+
import { coerceArray, filterControlKeys, filterNil, isBrowser, mergeDeep, wrapIntoObservable } from './utils';
34
import { EMPTY, merge, Observable, Subject, Subscription, timer } from 'rxjs';
4-
import { debounce, distinctUntilChanged, filter, map, mapTo } from 'rxjs/operators';
5-
import { deleteControl, findControl, handleFormArray, toStore } from './builders';
6-
import { Config, NgFormsManagerConfig, NG_FORMS_MANAGER_CONFIG } from './config';
5+
import { debounce, distinctUntilChanged, filter, first, map, mapTo, take } from 'rxjs/operators';
76
import { FormsStore } from './forms-manager.store';
8-
import { isEqual } from './isEqual';
97
import { Control, ControlFactory, FormKeys, HashMap, UpsertConfig } from './types';
10-
import { coerceArray, filterControlKeys, filterNil, isBrowser, mergeDeep } from './utils';
8+
import { Config, NG_FORMS_MANAGER_CONFIG, NgFormsManagerConfig } from './config';
9+
import { isEqual } from './isEqual';
10+
import { deleteControl, findControl, handleFormArray, toStore } from './builders';
11+
import { LocalStorageManager } from "./localStorageManager";
1112

1213
const NO_DEBOUNCE = Symbol('NO_DEBOUNCE');
1314

@@ -17,6 +18,7 @@ export class NgFormsManager<FormsState = any> {
1718
private valueChanges$$: Map<keyof FormsState, Subscription> = new Map();
1819
private instances$$: Map<keyof FormsState, AbstractControl> = new Map();
1920
private initialValues$$: Map<keyof FormsState, any> = new Map();
21+
private persistManager = new LocalStorageManager();
2022
private destroy$$ = new Subject();
2123

2224
constructor(@Optional() @Inject(NG_FORMS_MANAGER_CONFIG) private config: NgFormsManagerConfig) {
@@ -490,7 +492,7 @@ export class NgFormsManager<FormsState = any> {
490492
*
491493
* @example
492494
*
493-
* Removes the control from the store and from LocalStorage
495+
* Removes the control from the store and from given PersistStorageManager
494496
*
495497
* manager.clear('login');
496498
*
@@ -535,13 +537,16 @@ export class NgFormsManager<FormsState = any> {
535537
this.setInitialValue(name, control.value);
536538
}
537539

538-
if (isBrowser() && config.persistState && this.hasControl(name) === false) {
539-
const storageValue = this.getFromStorage(mergedConfig.storage.key);
540-
if (storageValue[name]) {
541-
this.store.update({
542-
[name]: mergeDeep(toStore(name, control), storageValue[name]),
543-
} as Partial<FormsState>);
544-
}
540+
if ((isBrowser() || !(config.persistManager instanceof LocalStorageManager)) && config.persistState && this.hasControl(name) === false) {
541+
this.persistManager = config.persistManager || this.persistManager;
542+
this.getFromStorage(mergedConfig.storage.key).subscribe(value => {
543+
const storageValue = value;
544+
if (storageValue[name]) {
545+
this.store.update({
546+
[name]: mergeDeep(toStore(name, control), storageValue[name]),
547+
} as Partial<FormsState>);
548+
}
549+
});
545550
}
546551

547552
/** If the control already exist, patch the control with the store value */
@@ -593,19 +598,26 @@ export class NgFormsManager<FormsState = any> {
593598
}
594599

595600
private removeFromStorage() {
596-
localStorage.setItem(this.config.merge().storage.key, JSON.stringify(this.store.getValue()));
601+
wrapIntoObservable(this.persistManager.setValue(
602+
this.config.merge().storage.key,
603+
this.store.getValue()
604+
)).pipe(first()).subscribe()
597605
}
598606

599607
private updateStorage(name: keyof FormsState, value: any, config) {
600608
if (isBrowser() && config.persistState) {
601-
const storageValue = this.getFromStorage(config.storage.key);
602-
storageValue[name] = filterControlKeys(value);
603-
localStorage.setItem(config.storage.key, JSON.stringify(storageValue));
609+
this.getFromStorage(config.storage.key).pipe(first()).subscribe(valueFromStorage => {
610+
const storageValue = valueFromStorage;
611+
storageValue[name] = filterControlKeys(value);
612+
wrapIntoObservable(this.persistManager.setValue(config.storage.key, storageValue)).pipe(first()).subscribe();
613+
});
604614
}
605615
}
606616

607617
private getFromStorage(key: string) {
608-
return JSON.parse(localStorage.getItem(key) || '{}');
618+
return wrapIntoObservable(this.persistManager.getValue(key)).pipe(
619+
take(1),
620+
);
609621
}
610622

611623
private deleteControl(name: FormKeys<FormsState>) {
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { PersistManager } from "./types";
2+
3+
export class LocalStorageManager<T> implements PersistManager<T> {
4+
setValue(key: string, data: T): T {
5+
localStorage.setItem(key, JSON.stringify(data));
6+
return data;
7+
}
8+
9+
getValue(key: string): T {
10+
return JSON.parse(localStorage.getItem(key) || '{}');
11+
}
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { PersistManager } from "./types";
2+
3+
export class SessionStorageManager<T> implements PersistManager<T> {
4+
setValue(key: string, data: T): T {
5+
sessionStorage.setItem(key, JSON.stringify(data));
6+
return data;
7+
}
8+
9+
getValue(key: string): T {
10+
return JSON.parse(sessionStorage.getItem(key) || '{}');
11+
}
12+
}

projects/ngneat/forms-manager/src/lib/types.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { AbstractControl } from '@angular/forms';
2+
import { Observable } from "rxjs";
23

34
export type Control<T = any> = Pick<
45
AbstractControl,
@@ -21,9 +22,17 @@ export interface HashMap<T = any> {
2122

2223
export type FormKeys<FormsState> = keyof FormsState | (keyof FormsState)[];
2324

24-
export interface UpsertConfig {
25+
export interface UpsertConfig<T = any> {
2526
persistState?: boolean;
2627
debounceTime?: number;
28+
persistManager?: PersistManager<T>
2729
arrControlFactory?: ControlFactory | HashMap<ControlFactory>;
2830
withInitialValue?: boolean;
2931
}
32+
33+
export interface PersistManager<T> {
34+
getValue(key: string): MaybeAsync<T>;
35+
setValue(key: string, value: T): MaybeAsync<T>;
36+
}
37+
38+
export type MaybeAsync<T = any> = Promise<T> | Observable<T> | T;

projects/ngneat/forms-manager/src/lib/utils.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Observable } from 'rxjs';
1+
import { from, isObservable, Observable, of } from 'rxjs';
22
import { filter } from 'rxjs/operators';
33

44
export type Diff<T, U> = T extends U ? never : T;
@@ -103,3 +103,15 @@ export function mergeDeep(target, ...sources) {
103103

104104
return mergeDeep(target, ...sources);
105105
}
106+
107+
export function isPromise(value: any): value is Promise<unknown> {
108+
return typeof value?.then === 'function';
109+
}
110+
111+
export function wrapIntoObservable<T>(value: T | Promise<T> | Observable<T>): Observable<T> {
112+
if (isObservable(value) || isPromise(value)) {
113+
return from(value);
114+
}
115+
116+
return of(value);
117+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
export { NgFormsManager } from './lib/forms-manager';
22
export { setAsyncValidators, setValidators } from './lib/validators';
33
export { NgFormsManagerConfig, NG_FORMS_MANAGER_CONFIG } from './lib/config';
4+
export { PersistManager } from './lib/types';
5+
export { LocalStorageManager } from './lib/localStorageManager';
6+
export { SessionStorageManager } from './lib/sessionStorageManager';

0 commit comments

Comments
 (0)