1
votes

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))
                                )
                        }
                    })
                )
            );
        })
    );
1

1 Answers

1
votes

You can do 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) => {
            // Return value as an observable with of()
            if (data.items.length > 0) {
                return of([new actions.Success(data.items)]);
            }

            const localData: any[] = localStorage.getItem(...);

            if (localData != null) {
                // Return value as an observable with of()
                return of([new actions.SuccessAction(localData)]);
            }

            // Return value as an observable with 
            return this.apiService.getAll()
                .pipe(
                    map(result => [new actions.Success(result) as any]),
                    catchError(error => [actions.Failure(error) as any])
                );
        }),
        // Flat map the observable result from the switchMap. This will flatten 
        // your result to an actual value.
        flatMap(data => data)
    );