2

reducer:

export const reducer = (state = initialstate, action: any) => {
  switch (action.type) {
    case ADD_USER: {
      return {
        ...state,
        userAdded: false
        }
      },
    case ADD_USER_SUCCESS: {
      return {
        ...state,
        user: action.payload
        userAdded: true
        }
      },
    case ADD_USER_FAIL: {
      return {
        ...state,
        userAdded: false
        }
      }  
    }
}

Effect:

login$ = createEffect(() =>
    this.actions$.pipe(
      ofType(UserAction.ADD_USER),
      exhaustMap(action =>
        this.userService.addUser("USER").pipe(
          map(user => UserAction.AddUserSuccess({ "user" })),
          catchError(error => of(UserAction.AddUserFail({ error })))
        )
      )
    )
  );

component.ts:

onClickAddUser(): void {
  this.store.dispatch(new AddUser('USER'));
  this.store.pipe(select(getUser), take(1)).subscribe((isUserAdded) => {
    if(isUserAdded) {
      this.router.navigateByUrl('/success'); // Expectation is to navigate to success page
    } else {
      this.router.navigateByUrl('/home'); // for first time it's always going to home screen even the success action being dispatched and the value been set to true.
    }
  });
}

on click of the method, an action being dispatched and following up with an effect, my case the api call is success and an success action being dispatched as well (in my reducer I set a flag to true), right after the AddUser action being dispatched from the click method, I'm subscribing to the flag(isUserAdded) to navigate the user to /success screen if the API return success response, in my case by the time i'm subscribing to the flag it's not updated in the store and hence the user navigated to home screen (but the expectation is to navigate to success screen as the API is success). Is that possible to wait for the value to updated in the store and then subscribe to it or is there any best practice to handle this scenario ??

I can write an effect to navigate the user once the success action being dispatched but I mean I do have other functionalities to handle once the flag set true, hence has to do everything in the component.

Ramana
  • 1,692
  • 3
  • 18
  • 34

3 Answers3

2

The sequence of events is as follows:

  1. You dispatch an AddUser action
this.store.dispatch(new AddUser('USER'));
  1. Reducer is called, the state is mutated and userAdded is set to false
    case ADD_USER: {
      return {
        ...state,
        userAdded: false
        }
      },
  1. selectors are called and subscribers are notified but you do not have any subscribtions yet
  2. Effect ADD_USER is called and async request is sent to userService
login$ = createEffect(() =>
    this.actions$.pipe(
      ofType(UserAction.ADD_USER),
      exhaustMap(action =>
        this.userService.addUser("USER").pipe(
          map(user => UserAction.AddUserSuccess({ "user" })),
          catchError(error => of(UserAction.AddUserFail({ error })))
        )
      )
    )
  );
  1. You subscribe to getUser selector with take(1) operator in a pipe
  this.store.pipe(select(getUser), take(1)).subscribe((isUserAdded) => {
    if(isUserAdded) {
      this.router.navigateByUrl('/success');
    } else {
      this.router.navigateByUrl('/home');
    }
  });
  1. Selector returns a value of the userAdded flag from the store which is false, your callback function is called, subscription is cancelled by take(1) operator
  2. Router navigates to '/home'
  3. Response from userService is returned and userAdded flag is set to true but your subscription is already canceled

If you want a simple solution right in component.ts, just try to subscribe with take(2), skip(1):

  this.store.pipe(select(getUser), take(2), skip(1)).subscribe((isUserAdded) => {
    if(isUserAdded) {
      this.router.navigateByUrl('/success');
    } else {
      this.router.navigateByUrl('/home');
    }
  });
Alex Shavlovsky
  • 321
  • 3
  • 8
  • A quick question, does take and skip dependent on the amount of time being taken by the API call ?? For example if the API returns response in 1 minute, does it wait for the call to be done and then subscribe or ?? – Kumar Oct 14 '20 at 05:40
  • No, [take(2)](https://rxjs-dev.firebaseapp.com/api/operators/take) and [skip(1)](https://rxjs-dev.firebaseapp.com/api/operators/skip) operators are independent of time. The take(2) operator takes first two values emmited by the Store and then unsuscribes. The skip(1) operator skips the first of these two emissions. Thus only the second emission is passed to your callback function – Alex Shavlovsky Oct 14 '20 at 17:54
  • Thanks for the clarification, I have similar scenario, but my case I have to check for both success and failure, on a fail response, in the effect, I'm dispatching an action to set the flag to false like initial state, and I tried take(2) and skip(1), is working for success scenario but not for fail scenario, does it possible to handle the fail case with take(2) and skip(1) ?? – Kumar Oct 15 '20 at 18:52
0

Can’t you just return two action in your effect fn like UserAddedSuccess and in catchError UserAddedFail write another effect which will listen useraddedsuccess action and redirect desired page on success , on first effects catcherror return UserAddedFail action and same process ?

Talha Akca
  • 394
  • 2
  • 8
  • I mean I do have other functionalities to handle once the flag set true, hence has to do everything in the component. – Ramana Oct 09 '20 at 18:15
  • Can you give more details about those functionalities so we can consider about solution ? . When you subscribe flag on onclick its almost impossible to receive after response value – Talha Akca Oct 09 '20 at 18:36
  • There are many conditional logic involved, after the AddUser action being dispatched, once the API returned successful response, display a modal which has bunch of details and collect the info call other API etc... – Ramana Oct 09 '20 at 20:00
0

I guess you can just dispatch multiple actions, you have to create separate action to handle routing.

login$ = createEffect(() =>
    this.actions$.pipe(
      ofType(UserAction.ADD_USER),
      exhaustMap(action =>
        this.userService.addUser("USER").pipe(
          switchMap(user => [
            UserAction.AddUserSuccess({ "user" }),
            routeAction.routeto('success')
          ])
          ,catchError(error => [
            UserAction.AddUserFail({ error }),
            routeAction.routeto('Home');
          ])
        )
      )
    )
  );
Priyamal
  • 2,919
  • 2
  • 25
  • 52
  • Like I said in my post, I can tweak the effect to navigate the user upon success action been dispatched but my intention is to do the logic in the component after AddUser action being dispatched and wait till the userService returns the success response, and in the effect success action will dispatch that will set userAdded to true in the reducer then subscribe to it and handle some other logic in the component in the same method. Hope my question was clear ! – Ramana Oct 11 '20 at 08:22