Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(lib): allow to add custom storage #28

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 12 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,53 +256,25 @@ formsManager.upsert(formName, abstractContorl, {
});
```

By default, the state is persisted to `localStorage` ([Link](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage)).

For storage to `sessionStorage` ([Link](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage)), add `FORMS_MANAGER_SESSION_STORAGE_PROVIDER` to the providers array in `app.module.ts`:
Library use `@ngneat/storage`([Link](https://github.com/ngneat/storage)) library which provides LocalStorageManager and SessionStorageManager. It's possible to store the form value into a custom storage. Just implement the PersistManager interface which is part of `@ngneat/storage` and use it when calling the upsert function. Storage implementation support Observables and Promises.

```ts
import { FORMS_MANAGER_SESSION_STORAGE_PROVIDER } from '@ngneat/forms-manager';
export class StateStoreManager<T> implements PersistManager<T> {
setValue(key: string, data: T) {
...
}

@NgModule({
declarations: [AppComponent],
imports: [ ... ],
providers: [
...
FORMS_MANAGER_SESSION_STORAGE_PROVIDER,
getValue(key: string) {
...
],
bootstrap: [AppComponent],
})
export class AppModule {}
}
}
```

Furthermore, a **custom storage provider**, which must implement the `Storage` interface ([Link](https://developer.mozilla.org/en-US/docs/Web/API/Storage)) can be provided through the `FORMS_MANAGER_STORAGE` token:

```ts
import { FORMS_MANAGER_STORAGE } from '@ngneat/forms-manager';

class MyStorage implements Storage {
public clear() { ... }
public key(index: number): string | null { ... }
public getItem(key: string): string | null { ... }
public removeItem(key: string) { ... }
public setItem(key: string, value: string) { ... }
}

@NgModule({
declarations: [AppComponent],
imports: [ ... ],
providers: [
...
{
provide: FORMS_MANAGER_STORAGE,
useValue: MyStorage,
},
...
],
bootstrap: [AppComponent],
})
export class AppModule {}
formsManager.upsert(formName, abstractContorl, {
persistState: true,
persistManager: new StateStoreManager(),
});
```

## Validators
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@angular/platform-browser-dynamic": "~9.1.11",
"@angular/router": "~9.1.11",
"@ngneat/lib": "^1.0.1",
"@ngneat/storage": "^1.0.0",
"@schuchard/prettier": "^3.1.0",
"core-js": "^2.5.4",
"ngx-semantic-version": "^1.2.1",
Expand Down
67 changes: 40 additions & 27 deletions projects/ngneat/forms-manager/src/lib/forms-manager.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { Inject, Injectable, Optional } from '@angular/core';
import { AbstractControl, FormArray, FormGroup } from '@angular/forms';
import { AbstractControl, FormGroup, FormArray } from '@angular/forms';
import { coerceArray, filterControlKeys, filterNil, isBrowser, mergeDeep } from './utils';
import { EMPTY, merge, Observable, Subject, Subscription, timer } from 'rxjs';
import { debounce, distinctUntilChanged, filter, map, mapTo } from 'rxjs/operators';
import { deleteControl, findControl, handleFormArray, toStore } from './builders';
import { Config, NgFormsManagerConfig, NG_FORMS_MANAGER_CONFIG } from './config';
import { debounce, distinctUntilChanged, filter, first, map, mapTo, take } from 'rxjs/operators';
import { FormsStore } from './forms-manager.store';
import { isEqual } from './isEqual';
import { Control, ControlFactory, FormKeys, HashMap, UpsertConfig } from './types';
import { coerceArray, filterControlKeys, filterNil, isBrowser, mergeDeep } from './utils';
import { FORMS_MANAGER_STORAGE } from './injection-tokens';
import { Config, NG_FORMS_MANAGER_CONFIG, NgFormsManagerConfig } from './config';
import { isEqual } from './isEqual';
import { deleteControl, findControl, handleFormArray, toStore } from './builders';
import { LocalStorageManager } from '@ngneat/storage';
import { wrapIntoObservable } from '@ngneat/storage/lib/utils';

const NO_DEBOUNCE = Symbol('NO_DEBOUNCE');

Expand All @@ -19,12 +20,10 @@ export class NgFormsManager<FormsState = any> {
private valueChanges$$: Map<keyof FormsState, Subscription> = new Map();
private instances$$: Map<keyof FormsState, AbstractControl> = new Map();
private initialValues$$: Map<keyof FormsState, any> = new Map();
private persistManager = new LocalStorageManager();
private destroy$$ = new Subject();

constructor(
@Optional() @Inject(NG_FORMS_MANAGER_CONFIG) private config: NgFormsManagerConfig,
@Inject(FORMS_MANAGER_STORAGE) private readonly browserStorage?: Storage
) {
constructor(@Optional() @Inject(NG_FORMS_MANAGER_CONFIG) private config: NgFormsManagerConfig) {
this.store = new FormsStore({} as FormsState);
}

Expand Down Expand Up @@ -495,7 +494,7 @@ export class NgFormsManager<FormsState = any> {
*
* @example
*
* Removes the control from the store and from browser storage
* Removes the control from the store and from given PersistStorageManager
*
* manager.clear('login');
*
Expand Down Expand Up @@ -540,13 +539,20 @@ export class NgFormsManager<FormsState = any> {
this.setInitialValue(name, control.value);
}

if (isBrowser() && config.persistState && this.hasControl(name) === false) {
const storageValue = this.getFromStorage(mergedConfig.storage.key);
if (storageValue[name]) {
this.store.update({
[name]: mergeDeep(toStore(name, control), storageValue[name]),
} as Partial<FormsState>);
}
if (
(isBrowser() || !(config.persistManager instanceof LocalStorageManager)) &&
config.persistState &&
this.hasControl(name) === false
) {
this.persistManager = config.persistManager || this.persistManager;
this.getFromStorage(mergedConfig.storage.key).subscribe(value => {
const storageValue = value;
if (storageValue[name]) {
this.store.update({
[name]: mergeDeep(toStore(name, control), storageValue[name]),
} as Partial<FormsState>);
}
});
}

/** If the control already exist, patch the control with the store value */
Expand Down Expand Up @@ -598,22 +604,29 @@ export class NgFormsManager<FormsState = any> {
}

private removeFromStorage() {
this.browserStorage?.setItem(
this.config.merge().storage.key,
JSON.stringify(this.store.getValue())
);
wrapIntoObservable(
this.persistManager.setValue(this.config.merge().storage.key, this.store.getValue())
)
.pipe(first())
.subscribe();
}

private updateStorage(name: keyof FormsState, value: any, config) {
if (isBrowser() && config.persistState) {
const storageValue = this.getFromStorage(config.storage.key);
storageValue[name] = filterControlKeys(value);
this.browserStorage?.setItem(config.storage.key, JSON.stringify(storageValue));
this.getFromStorage(config.storage.key)
.pipe(first())
.subscribe(valueFromStorage => {
const storageValue = valueFromStorage;
storageValue[name] = filterControlKeys(value);
wrapIntoObservable(this.persistManager.setValue(config.storage.key, storageValue))
.pipe(first())
.subscribe();
});
}
}

private getFromStorage(key: string) {
return JSON.parse(this.browserStorage?.getItem(key) || '{}');
return wrapIntoObservable(this.persistManager.getValue(key)).pipe(take(1));
}

private deleteControl(name: FormKeys<FormsState>) {
Expand Down
4 changes: 3 additions & 1 deletion projects/ngneat/forms-manager/src/lib/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AbstractControl } from '@angular/forms';
import { PersistManager } from '@ngneat/storage';

export type Control<T = any> = Pick<
AbstractControl,
Expand All @@ -21,9 +22,10 @@ export interface HashMap<T = any> {

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

export interface UpsertConfig {
export interface UpsertConfig<T = any> {
persistState?: boolean;
debounceTime?: number;
persistManager?: PersistManager<T>;
arrControlFactory?: ControlFactory | HashMap<ControlFactory>;
withInitialValue?: boolean;
}
2 changes: 1 addition & 1 deletion projects/ngneat/forms-manager/src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Observable } from 'rxjs';
import { from, isObservable, Observable, of } from 'rxjs';
import { filter } from 'rxjs/operators';

export type Diff<T, U> = T extends U ? never : T;
Expand Down
Loading