angulartypescriptngrxangular-router-guardsngrx-entity

Angular ngrx: guards with side-effect


I have guard and now I need to catch error in it from a side-effect which calls an http service and redirects the user to an error state. I tried to add catchError, but I don't even go there if I get an error from server side.

guard.ts

canActivate(): Observable<boolean> {
    return this.store
        .select(fromDataStore.getDatasLoaded)
        .pipe(
            withLatestFrom(this.store.select(fromDataStore.getDataLoading)),
            map(([loaded, loading]) => {
                if (!loaded && !loading) {
                    this.store.dispatch(new fromDataStore.LoadData());
                }
                return loaded;
            }),
            filter(loaded => loaded),
            take(1),
            catchError(error => {
                // redirect user to error state
            })
        );
}

effect.ts

 @Effect() loadData$: Observable<Action> = this.actions$.ofType(DataActionTypes.LoadData)
    .pipe(
        mergeMap(() =>
            this.dataApiService.loadData()
                .pipe(
                    map(data => ({
                        type: DataActionTypes.LoadDataSuccess,
                        payload: data
                    })),
                    catchError(() => of({ type: DataActionTypes.LoadDataFailure }))
                )
        )
    );

reducer.ts

case DataActionTypes.LoadData: {
        return {
            ...state,
            data: {
                ...state.data,
                isLoading: true
            }
        };
    }
    case DataActionTypes.LoadDataSuccess: {
        return {
            ...state,
            data: {
                ...dataAdapter.addMany(action.payload, state.data),
                isLoaded: true,
                isLoading: false
            }
        };
    }
    case DataActionTypes.LoadDataFailure: {
        return {
            ...state,
            data: {
                ...state.data,
                isLoaded: true,
                isLoading: false
            }
        };
    }
    default: {
        return state;
    }

Solution

  • When you handle LoadDataFailure in the reducer, you can add the error to the state.

    In the guard you can add a withLatestFrom and select that part of the state, with the error. Instead of catchError, there is no error here so it will not catch anything.

    If you don't have an error you can navigate to the page, if you have an error redirect the user to what you need.

    You could also refactor a bit the code, like always trigger the load action when the guard enters, move the if statement outside, and work with isDataLoaded stream.

    Something like:

    this.store.dispatch(new fromDataStore.LoadData());
    
    return this.store
            .select(fromDataStore.getDatasLoaded)
            .filter(Boolean),
            .withLatestFrom(fromDataStore.getError)
            ...