async-storage-plugin
async-storage-plugin copied to clipboard
Plugin reverts last changes on other states
Let's imagine we have 2 states: A and B
interface TestModel {
conter: number;
}
@State<TestModel>({
name: 'a',
defaults: {
counter: 0,
},
})
export class AState {}
@State<TestModel>({
name: 'b',
defaults: {
counter: 0,
},
})
export class BState {}
The app is configured to save the BState changes via HTTP on the backend
NgxsAsyncStoragePluginModule.forRoot(
MyEngine,
{
key: 'b',
}
),
Let's imagine it takes 1 second to pull data from the backend.
After the initialization we have next state tree:
{
a: {
counter: 0
},
b: {
counter: 0
}
}
Then we start pulling the B state which has the counter value 1 on the backend.
While B state is pulling we have updated several times the state A:
{
a: {
counter: 3
},
b: {
counter: 0
}
}
Then B state data received and plugin updates the state. And it reverts our changes from state A:
{
a: {
counter: 0
},
b: {
counter: 1
}
}
That is because of: https://github.com/ngxs-labs/async-storage-plugin/blob/39fb6ecb0720a6be05b86e4a360f3bc88d4e4dda/src/lib/async-storage.plugin.ts#L71
where 'previousState' is the state that was at the moment of pulling, not the latest one.
The expected behavior is to update the B state only:
{
a: {
counter: 3
},
b: {
counter: 1
}
}
I currently work around this issue by waiting for the init action to be fired (as this plugin hijacks the init action). This kind of makes sense as I first want to load in the, in my case indexeddb, data before I start to execute a bunch of selects/actions.
I created a Store wrapper that handles this:
import {Type} from '@angular/core';
import {Actions, InitState, ofActionCompleted, Store} from '@ngxs/store';
import {Observable} from 'rxjs';
import {first, mergeMap, shareReplay} from 'rxjs/operators';
export class AsyncStore {
initCompleted$ = this._actions$.pipe(ofActionCompleted(InitState), first(), shareReplay(1));
constructor(private _store: Store, private _actions$: Actions) {}
dispatch(event: any|any[]): Observable<any> {
const observable =
this.initCompleted$.pipe(mergeMap(() => this._store.dispatch(event)), shareReplay(1));
observable.subscribe();
return observable;
}
selectOnce<T>(selector: (state: any, ...states: any[]) => T): Observable<T>;
// tslint:disable-next-line: unified-signatures
selectOnce<T = any>(selector: string|Type<any>): Observable<T>;
selectOnce(selector: any): Observable<any> {
return this.initCompleted$.pipe(mergeMap(() => this._store.selectOnce(selector)));
}
select<T>(selector: (state: any, ...states: any[]) => T): Observable<T>;
// tslint:disable-next-line: unified-signatures
select<T = any>(selector: string|Type<any>): Observable<T>;
select(selector: any): Observable<any> {
return this.initCompleted$.pipe(mergeMap(() => this._store.select(selector)));
}
reset(state: any): any {
this.initCompleted$.pipe(mergeMap(() => this._store.reset(state)));
}
}
Hello, I deal with memory limit of localStorage using sync storage-plugin. I tried to replace the default storage plugin with this async storage plugin and related indexedDb behind. Unfortunatelly did not pass the "regretion tests". I run in couple of issues probably caused by this bug.
I have a scenario where I fetch/hydrate multiple related entity subsets from Rest Api and use loaded status mark for each entity. After all are fetched (loaded = true for all) I prepare component view with that data.
When I use persitency per key NgxsAsyncStoragePluginModule.forRoot( { key: ['ProductPrices', 'ProductData"...]}
I never get all loading status = true. Seem that subsequent fetch erases previously fetched entity from STATE.
When I use global @@STATE persistency the behaviour is different. By first page load all entity are correctly fetched from Rest API and set to loaded=true in stores. But when I reload the page, some entity are reused from indexedDB, and some are forced to fetch via Rest again.. I reconfirmed it is random behaviour and it depends in which order the result are recieved.
Do You think it is a matter of simple fix of mentioned nextState = setValue(previousState, key, val);
implementation? Or could this be conceptual problem connected to async execution where the order of getItems, setItems, select execution is not guranteed. So it can break the store consistency based on funtional paradigm and sequential application of sync reducers. Is that possible?
I see the workaround. Maybe it could help in case we have only one time initial data hydration from rest api. But in case I fetch related entity subsets based on user query dynamically multiple times during the store lifecycle? Is possible to implement selectSnapShot in workaround?
BTW: Is there any simple way how to use IndexedDb with ngxs? Even with sync performance penalty? The same speed as localStorage, just to circumvent its memory limitation? Anybody tried async - await implementaion of storage interface which assures seguential storage manipulation?
In our situation, we are seeing @@INIT
fire after the application has loaded. This has the unfortunate side-effect of wiping our data.