Search code examples
angularrxjsobservablengrxngrx-effects

How to combine concatMap and exhaustMap in elegant way?


I have a CreateReviewForm with reCaptcha and I have 3 streams for it:

  • ngRx action (addReview) (1st observable). Everytime when this action is triggered I have to call this.recaptchaV3Service.execute() method which returns observable with token (string) (2nd observable). And then using this token I have to call API method for creating review this.reviewApiService.create({ ...review, recaptchaValue: token }) which returns observable with created review (3rd observable)

First I tried it with combineLatest and it didn't work like I expected. Method for getting token emited value only when I clicked "Add Review" btn first time. All next times it didn't emit any value (withLatestFrom worked in the same way. I tried it also)

createReview$ = combineLatest([
    this.actions$.pipe(
        ofType(ReviewsDialogActions.addReview)
    ),
    this.recaptchaV3Service.execute('AddREview')
]).pipe(exhaustMap(([{ review }, token]) => 
    this.reviewApiService.create({ ...review, recaptchaValue: token })))
        .pipe(
            map(createdReview => ReviewsDialogApiActions.createReviewSuccess({ createdReview, companyId: createdReview.company.id })),
            catchError(error => {
                debugger;
                return observableOf(ReviewsDialogApiActions.createReviewError({ error: error.error.message.join(', ') }))
            })

)

I combined it all with concatMap and exhaustMap and it works but I don't like how it looks and I don't think it should look like this

createReview$ = createEffect((): any => {
let reviewFormData = {};
return this.actions$.pipe(
    ofType(ReviewsDialogActions.addReview),
    concatMap(({ review }) => {
        reviewFormData = review;
        return this.recaptchaV3Service.execute('AddREview');
    }),
    exhaustMap((token) => this.reviewApiService.create({ ...<Review>reviewFormData, recaptchaValue: token })
        .pipe(map(createdReview => ReviewsDialogApiActions.createReviewSuccess({ createdReview, companyId: createdReview.company.id })),
        catchError(error => {
            return observableOf(ReviewsDialogApiActions.createReviewError({ error: error.error.message.join(', ') }))
        })))
)

}

Please help which is the best combination here for rxJs operators?


Solution

  • You could use the pipeable operator combineLatestWith like this:

    createReview$ = createEffect(() => this.actions$.pipe(
        ofType(ReviewsDialogActions.addReview),
        combineLatestWith(this.recaptchaV3Service.execute('AddREview')),
        exhaustMap(([reviewFormData, token]) => this.reviewApiService.create(..)
            ...
        )
    )
    

    Cheers