Andrei V
Andrei V

Reputation: 1538

ngrx updating state from effects

I have ngrx store setup that stores currently active user and user's locale. Based on user locale my interceptor will inject X-LOCALE header in all API calls. When action to select user is called I first call separate API to get locale for that user, pass that locale to interceptor and then proceed on calling rest of APIs (at this point X-LOCALE header should be injected). Question: how do I trigger change of the state from user-effects.ts?

user-reducer.ts

export interface UsersState extends EntityState<UserInfo> {
  loading: boolean;
  currentUser: UserInfo;
  locale: string;
}
const usersReducer = createReducer(
  INITIAL_STATE,
  on(actions.fetchUser, 
  ...bunch of other actions...,
  (state) => ({ ...state, loading: true })
);

user-actions.ts

...bunch of actions
export const fetchUser = createAction('[USERS] Fetch User Request', props<{ uuid: string }>());

user-effects.ts

@Injectable()
export class UsersEffects {
  fetchUser$ = createEffect(() =>
    this.actions$.pipe(
      ofType(usersActions.fetchUser),
      switchMap(({ uuid }) => {
        // here we would read user locale
        return this.usersService.fetchUserLocale(uuid).pipe(
          map((locale) => {
            // ******* here i need to update locale in the state ********
            return uuid;
          });
        }),
      );
    }),
    switchMap((uuid) => {
      // all calls from here on should have "X-LOCALE" header injected
      return forkJoin([
        this.usersService.fetchUser(uuid),
        this.usersService.fetchUserCards(uuid),
        this.usersService.fetchUserPermissions(uuid),
      ]).pipe(
        map(([userDto, cards, permissions]) => {
          const userInfo = { ...userDto, cards, permissions };
          return usersActions.fetchUserSuccess({ userInfo });
        }),
        catchError((error) => of(usersActions.fetchUserFailure({ error }))),
      );
    }),
  ),
);

and here locale.interceptor.ts that would inject headers

@Injectable()
  export class HttpHeadersInterceptor implements HttpInterceptor {
    constructor(private store$: Store<UsersState>) {
      this.store$.pipe(select((state) => state.locale)).subscribe((locale: string) => {
        this.locale = locale;
      });
    }
    private locale: string;

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
      const defaultHeaders = { Accept: 'application/json, text/plain' };

      if (this.locale) {
        defaultHeaders['X-LOCALE'] = this.locale;
      }

      const authReq = req.clone({ setHeaders: defaultHeaders });

      return next.handle(authReq);
    }
  }

Upvotes: 1

Views: 1574

Answers (1)

mat.hudak
mat.hudak

Reputation: 3193

Well I'm not sure if it's a best practice, but you still can dispatch an action from within the switchMap, be it directly from the map or you can change it to tap since there's no actual mapping, it's just a side effect. Something like this.

  switchMap(({ uuid }) => {
    // here we would read user locale
    return this.usersService.fetchUserLocale(uuid).pipe(
      tap((locale) => {
        this.store.dispatch(desiredAction({ uuid }));
      });
    }),
  );

As I said, I'm not sure if this is the best practice but in my project I used something similar on couple of occasions.

Upvotes: 2

Related Questions