1

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?

missbells
  • 43
  • 4
  • Not sure you should? If you follow ngrx principles, I guess this shouldn’t be needed? Action1 > effect1 > action2 > effect2 > action3 > reducer would make more sense if I understand you correctly? – MikeOne Jun 12 '22 at 17:32
  • I was thinking about adding one more effect/action. But I between these effects/actions I will loose reviews which I got from addReview action. Because I don't need those reviews for recapatcha request which is between addReview action and createReview api request – missbells Jun 12 '22 at 18:29
  • Can you not either pass that data with the action or store it with a Reducer? – MikeOne Jun 12 '22 at 18:34
  • I don't like that if I pass this reviews data with recaptcha call action then it will be not very elegant because I will not even use this data in the effect which will listen to this action. I will pass this data just because I will need to send it to the next action. it will be something similar like I already have in my concatMap + exhaustMap solution. But I don't know maybe it's a common practice? – missbells Jun 12 '22 at 18:49

1 Answers1

1

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

akotech
  • 1,961
  • 2
  • 4
  • 10
  • Using combineLatestWith in effect isn't the best idea because that service call will be executed immediately at app start. So even if that action was not triggered yet. combineLatestWith is an RxJs operator, which has an alternative from NgRx to this case, called: [concatLatestFrom](https://ngrx.io/api/effects/concatLatestFrom). Arrow function (() => ... ) is important inside that. – Zserrbo Aug 11 '23 at 13:37