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.
Advertisement
Answer
The sequence of events is as follows:
- You dispatch an
AddUser
action
this.store.dispatch(new AddUser('USER'));
- Reducer is called, the state is mutated and
userAdded
is set tofalse
case ADD_USER: { return { ...state, userAdded: false } },
- selectors are called and subscribers are notified but you do not have any subscribtions yet
- Effect
ADD_USER
is called and async request is sent touserService
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 }))) ) ) ) );
- You subscribe to
getUser
selector withtake(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'); } });
- Selector returns a value of the
userAdded
flag from the store which isfalse
, your callback function is called, subscription is cancelled bytake(1)
operator - Router navigates to ‘/home’
- Response from
userService
is returned anduserAdded
flag is set totrue
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'); } });