I'm caching some data using NGRX like this:
@Effect() public getProductList$ = this.actions$
.pipe(
ofType<actionType>(actions.ACTION_NAME),
withLatestFrom(this.store.select(x => x.storeSlice), (action: any, store: any) => store.data),
switchMap((data: any) => {
if (data.items.length > 0) {
return [new actions.Success(data.items)];
}
const localData: any[] = localStorage.getItem(...);
if (localData != null) {
return [new actions.SuccessAction(localData)];
}
else {
return this.apiService.getAll()
.pipe(
map(result => new actions.Success(result)),
catchError(error => actions.Failure(error));
}
})
);
Basically, I try to get the data from the store first, if the resulting array has items, I return a Success action with the data. Otherwise, I try to load the data from the localStorage and if it exists I return the success action with it. Finally, if there is no cached data, I call the API to get the data, and this can result in a Success or Failure action.
The problem is that the data grew too much and I can't store it in the localStorage anymore, so I need to use IndexedDb, which works with promises.
I created a wrapper service around the IndexedDb api and my method returns an Observable with the data. So I need to switch the const localData: any[] = localStorage.getItem(...); with my service call. With the localStorage everything is fine since it is synchronous, but the IndexedDb is not.
I tried something like this, but it does not work:
@Effect() public getProductList$ = this.actions$
.pipe(
ofType<actionType>(actions.ACTION_NAME),
withLatestFrom(this.store.select(x => x.storeSlice), (action: any, store: any) => store.data),
switchMap((data: any) => {
if (data.items.length > 0) {
return [new actions.Success(data.items)];
}
return this.idb.getData().map(localData => {
if (localData != null) {
return [new actions.SuccessAction(localData)];
}
else {
return this.apiService.getAll()
.pipe(
map(result => new actions.Success(result)),
catchError(error => actions.Failure(error));
}
});
})
);
This does not work because I'm returning an Observable instead of the actual action. But how could I extract the data out of the Observable? Is there an rxjs operator that could help me with this?
If anyone gets here in future, this was the solution:
@Effect() public getProductList$ = this.actions$
.pipe(
ofType<actionType>(actions.ACTION_NAME),
withLatestFrom(this.store.select(x => x.storeSlice), (action: any, store: any) => store.data),
switchMap((data: any) => {
if (data.items.length > 0) {
return [new actions.Success(data.items)];
}
return this.idb.getData()
.pipe(
map(data => new actions.SuccessAction(data),
switchMap(successActionFromIDB => {
if (successActionFromIDB.payload.length > 0) {
return of(successActionFromIDB);
}
else {
return this.apiService.getAll()
.pipe(
tap(data => this.idb.setData(data)),
map(data => new actions.SuccessAction(data)),
catchError(error => new actions.FailureAction(error))
)
}
})
)
);
})
);