Manugal
Manugal

Reputation: 15

Dispatch an action based on data coming from the previous action

I am struggling with dispatching an action after the previous one is completed.

What I want to do is dispatch an action (let's say getDetails), and then use data coming from this action to dispatch another action (let's say getUserLists). Then, when this last action completes, I want to navigate to a route.

I am not sure how to chain these actions. This is what I tried directly on my component (I heard that this kind of logic must be inside an effect, so my purpose here is only to show what I did):

this.store.dispatch(DetailsActions.getDetails({ bt: '00079138' }));
    this.store.select(fromApp.selectDetails)
      .pipe(
        // which operator I have to use here???
        tap((clientId) => this.store.dispatch(ProfileActions.getUsersList({ clientId })),
        first()
      )
      .subscribe(() => this.router.navigate(['tabs/home'], { replaceUrl: true }));

Is it correct the approach that I have taken? There are some best practices on this?

Thanks in advance.

UPDATE Tried @calimoose solution by implementing a new action that do the following:

this.actions$.pipe(
            ofType(ProfileActions.populateApiInfo),
            flatMap((data) => of(DetailsActions.getDetails({ bt: data.bt }))),
            withLatestFrom(this.store.select(fromApp.selectClientId)),
            flatMap(([_, clientId]) => of(ProfileActions.getUsersList({ clientId }))),
            withLatestFrom(this.store.select(fromApp.selectPbList)),
            map(([_, pbList]) => {
                return ProfileActions.populateApiInfoSuccess();
            })
        ));

But it doesn't seem to dispatch getDetails and getUsersList actions. Something wrong on operator's chaining?

UPDATE 2 Tried as @Friso suggested, but getDetails and getUsersList actions are not dispatched

   this.actions$.pipe(
            ofType(ProfileActions.populateApiInfo),
            concatMap((data) =>
                of(DetailsActions.getDetails({ bt: data.bt }))
                    .pipe(
                        withLatestFrom(this.store.select(fromApp.selectClientId)),
                        concatMap(([_, clientId]) =>
                            of(ProfileActions.getUsersList({ clientId }))
                                .pipe(
                                    withLatestFrom(this.store.select(fromApp.selectPbList)),
                                    map(([_, pbList]) => {
                                        return ProfileActions.populateApiInfoSuccess();
                                    })
                                )
                        ),
                    )
            )
        ));

Upvotes: 1

Views: 571

Answers (1)

calimoose
calimoose

Reputation: 135

Using an Effect which returns your next action would be a better option here. See also: docs

getUsersList$ = createEffect(() => this.actions$.pipe(
    ofType(DetailsActions.getDetails),
    withLatestFrom(*/ insert clientId Selector here */),
    map([_, clientId], => of(ProfileActions.getUsersList({ clientId }))
    )
  );

make sure to read up on the ngrx docs if you're not sure about the concept:

withLatestFrom starts listening for data right away, regardless of the action coming in, so you might want to use a flattening operator to prevent that from happening:

.pipe(
  ofType(thePreviousAction),
  concatMap(action => of(action).pipe(
    withLatestFrom(this.store.pipe(select(getYourData)))
  )),
  // do stuff
)

@Effect requires you to return a new action. In some cases you might not want to do so. You can use { dispatch: false } to ignore that requirement.

Upvotes: 2

Related Questions