Search code examples
ngrxangular2-observablesngrx-effects

Conditonally returning Observable<Action> within Ngrx Effect


I'm currently refactoring my code to include the ngrx store. To minimise the amount of API calls of my LoadDeals() action, I'm checking in an effect if the store is empty. Only if it is empty, go on and make an API call. I first tried to use this pattern found here on SO (https://stackoverflow.com/a/50527652/2879771).

I realised the downside is, that every LoadDeals() call is ignored if there is data in the store. To have the possibility of forcing a load, I included an optional boolean payload to the LoadDeals() class. If this is set to true, do call the API.

Here's my first try

@Effect() loadDeals$: Observable<Action> = this._actions$.pipe(
    ofType<LoadDeals>(actions.LOAD_DEALS),
    withLatestFrom(this._store.pipe(select(getDealsLoaded))),
    switchMap(([action, hasLoaded]) => {
        if (!hasLoaded || action.force) {
            return this._deals.deals.pipe(
                map(deals => new LoadDealsSuccess(deals)),
                catchError(error => of(new LoadDealsFail(error)))
            );
        } else {
            return of(new LoadDealsSkip());
        }
    })
);

But this yields the following error:

    Argument of type '([action, hasLoaded]: [LoadDeals, boolean]) => Observable<LoadDealsSuccess | LoadDealsFail> | Observable<LoadDealsSkip>' is not assignable to parameter of type '(value: [LoadDeals, boolean], index: number) => ObservableInput<LoadDealsSuccess | LoadDealsFail>'.
  Type 'Observable<LoadDealsSuccess | LoadDealsFail> | Observable<LoadDealsSkip>' is not assignable to type 'ObservableInput<LoadDealsSuccess | LoadDealsFail>'.
    Type 'Observable<LoadDealsSkip>' is not assignable to type 'ObservableInput<LoadDealsSuccess | LoadDealsFail>'.
      Type 'Observable<LoadDealsSkip>' is not assignable to type 'Iterable<LoadDealsSuccess | LoadDealsFail>'.
        Property '[Symbol.iterator]' is missing in type 'Observable<LoadDealsSkip>'.

Here is my deals.actions.ts

import { Action } from '@ngrx/store';
import { ITmsCpRecord } from '@models/cp';

export enum DealsActionTypes {
    LOAD_DEALS = '[Deals] Load Deals',
    LOAD_DEALS_FAIL = '[Deals API] Load Deals Fail',
    LOAD_DEALS_SUCCESS = '[Deals API] Load Deals Success',
    LOAD_DEALS_SKIP = '[Deals Store] Load Deals Skip (cached)'
}

export class LoadDeals implements Action {
    readonly type = DealsActionTypes.LOAD_DEALS;
    constructor(public force: boolean = false) {}
}
export class LoadDealsFail implements Action {
    readonly type = DealsActionTypes.LOAD_DEALS_FAIL;
    constructor(public payload: any) {}
}
export class LoadDealsSuccess implements Action {
    readonly type = DealsActionTypes.LOAD_DEALS_SUCCESS;
    constructor(public payload: ITmsCpRecord[]) {}
}
export class LoadDealsSkip implements Action {
    readonly type = DealsActionTypes.LOAD_DEALS_SKIP;
}

// action types
export type DealsAction = LoadDeals|LoadDealsFail|LoadDealsSuccess|LoadDealsSkip;

So I split it into separate effects listening to the same action but with a different filter operator. This works fine. Although I would like to not split it because it is some redundant code.

Does someone see my error? Cheers

@Effect() loadDeals$: Observable<Action> = this._actions$.pipe(
    ofType<LoadDeals>(actions.LOAD_DEALS),
    withLatestFrom(this._store.pipe(select(getDealsLoaded))),
    filter(([action, hasLoaded]) => !hasLoaded || action.force),
    switchMap(() => {
        return this._deals.deals.pipe(
            map(deals => new LoadDealsSuccess(deals)),
            catchError(error => of(new LoadDealsFail(error)))
        );
    })
);

@Effect() loadDealsSkip$: Observable<Action> = this._actions$.pipe(
    ofType<LoadDeals>(actions.LOAD_DEALS),
    withLatestFrom(this._store.pipe(select(getDealsLoaded))),
    filter(([action, hasLoaded]) => hasLoaded && !action.force),
    switchMap(() => of(new LoadDealsSkip()))
);


Solution

  • You will probably not like this answer, but I would suggest to create 2 actions for this. One load action like you already have, and another force load action.

    This would also mean creating 2 effects, but in the second one you won't have to select from store.