Search code examples
reduxrxjsobservablengrxngrx-effects

NGRX/Effects pass data through to catchError


Gist

I have an Effect, that loads the reviews of a product through multiple API requests:

@Effect()
    loadOfProduct$ = this.actions$.pipe(
        ofType<ReviewsActions.LoadOfProduct>(ReviewsActions.Types.LOAD_OF_PRODUCT),
        map(action => action.payload),
        switchMap((productId: string) => {
            return this.reviewsService.loadOfProduct(productId);
        }),
        switchMap((reviews: Review[]) => {
            return forkJoin([
                of(reviews),
                this.productsService.loadManyById(reviews.map(r => r.productId)),
                this.usersService.loadManyById(reviews.map(r => r.writerId)),
            ]);
        }),
        switchMap((data: any) => {
            const [reviews, products, users] = data;
            return [
                new ProductsActions.LoadManyByIdSuccess(products),
                new UsersActions.LoadManyByIdSuccess(users),
                new ReviewsActions.LoadOfProductSuccess(reviews),
            ];
        }),
        catchError(error => of(new ReviewsActions.LoadOfProductFail({
            ...error.error,
            // I need the productId here!!!
            // payload: productId,
        }))),
    );

Issue

The issue is, that I don't know how to access the productId string from the first switchMap in the catchError


Solution

  • Simply nest the next operators under the first switchMap:

        @Effect() loadOfProduct$ = this.actions$
        .pipe(
            ofType<ReviewsActions.LoadOfProduct>(ReviewsActions.Types.LOAD_OF_PRODUCT),
            switchMap((action) => {
                const productId = action.payload;
                return this.reviewsService.loadOfProduct(productId)
                    .pipe(
                        switchMap((reviews: Review[]) => {
                            return forkJoin([
                                of(reviews),
                                this.productsService.loadManyById(reviews.map(r => r.productId)),
                                this.usersService.loadManyById(reviews.map(r => r.writerId)),
                            ]);
                        }),
                        switchMap((data: any) => {
                            const [reviews, products, users] = data;
                            return [
                                new ProductsActions.LoadManyByIdSuccess(products),
                                new UsersActions.LoadManyByIdSuccess(users),
                                new ReviewsActions.LoadOfProductSuccess(reviews),
                            ];
                        }),
                        catchError(error => of(new ReviewsActions.LoadOfProductFail({
                            ...error.error,
                            // now you have the productId
                            // payload: productId,
                        })))
                    );
            })
        );
    

    Or if the reviewsService.loadOfProduct method returns a promise:

    return from(this.reviewsService.loadOfProduct(productId)) // originally fromPromise
        .pipe(...)
    

    This is also the right way to deal with errors inside effects, because in the original code if an error occurs the "effect" will stop working, even though it entered the catchError operator.

    For more on this take a look at ngrx effects error handling.