Platform: Initialize the state of a lazy reducer

Created on 25 Aug 2017  路  7Comments  路  Source: ngrx/platform

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  
[ ] Feature request
[ ] Documentation issue or request
[X] Support request

I am using ngrx/platform with ionic

I have the Auth reducer almost identical to the one in ngrx/platform's example however my module is lazy and auth reducer is imported like StoreModule.forFeature('auth', reducers).

The problem is that the Auth state is always undefined until the user go the _LoginPage_ (in other meaning until AuthModule is loaded) but the app should check if the user is logged in even if he didn't open the login page.

For instance I couldn't use store.select in my Auth Guard because auth state is undefined

export class AuthGuard {

  authenticated = false;

  constructor(public store: Store<any>) {

    /** Cannot use the following code because auth is undefined until user visit the login page
     *
     * this.store.select(fromAuth.getLoggedIn).take(1).map(authed => this.authenticated = authed);
     */

     // My current workaround:
    this.store.take(1).subscribe(state => {
      this.authenticated = !!(state && state.auth && state.auth.status && state.auth.status.loggedIn);
    });
  }

  /** Check if page can enter */
  ionViewCanEnter() {

    if (!this.authenticated) {
       this.store.dispatch(new Auth.LoginRedirect());
     }
     return this.authenticated;
  }
}

Root reducer

import { ActionReducerMap, ActionReducer } from '@ngrx/store';

export interface State {
}

export const reducers: ActionReducerMap<State> = {
};

Auth reducer

import { createSelector, createFeatureSelector } from '@ngrx/store';
import * as fromRoot from '../../reducers';
import * as fromAuth from './auth';
import * as fromAuthPage from './auth-page';

export interface AuthState {
  status: fromAuth.State;
  authPage: fromAuthPage.State;
}

export interface State extends fromRoot.State {
  auth: AuthState;
}

export const reducers = {
  status: fromAuth.reducer,
  authPage: fromAuthPage.reducer,
};
// ...

Auth module (lazy)

@NgModule({
  imports: [
    IonicPageModule.forChild(AuthPage),
    StoreModule.forFeature('auth', reducers),
    EffectsModule.forFeature([AuthEffects])
  ],
  declarations: [
    AuthPage
  ],
  exports: [
    AuthPage
  ]
})
export class AuthModule {
}

Is it possible to initialize Auth state in this case?

Thanks.

Most helpful comment

@MurhafSousli I agree with @MikeSaprykin. You need to have your AuthModule loaded eagerly in the AppModule, so its always available when the application is loaded. Authentication is not something you would lazy load as its essential to large parts of the app.

All 7 comments

I've ran into same issue recently on my project. Seems like the only right way is to move auth reducer from feature to root reducer, as you might probably need auth state and actions not only on auth page ( like log out, for instance, or getting user token/info somewhere else in the app ). Or if you want auth to be a separate slice of state ( not to be included in root reducers state object ), you can use StoreModule.forFeature() in the same module, where you provide StoreModule.forRoot().
Hope that helps!
Best regards and have fun! :-)

Or you can try to move use store.select logic from constructor to ionViewCanEnter method. But that should work only if that loads your lazy loaded module with auth reducer anyways, no matter if guard gave you positive or negative result.
(I'm not sure about that with ionic, but Angulars canActivate routing guard method doesn't prevent lazy loading module from being loaded, thus you'll have your chunk downloaded and reducer bind to store. canLoad method prevents loading though :-)

@MikeSaprykin Thanks, I didn't know that we can import StoreModule twice one forRoot and one forFeature in the same module

@MurhafSousli I agree with @MikeSaprykin. You need to have your AuthModule loaded eagerly in the AppModule, so its always available when the application is loaded. Authentication is not something you would lazy load as its essential to large parts of the app.

I just ran into a similar issue and found a way to address it (below); any guidance on if I'm way off base on this solution would be appreciated:

app.module:
StoreModule.forRoot(initialReducerMap, { initialState: getInitialState })

app.state:

export function defaultReducer<T>(state: T) { return state; }

export const initialReducerMap = {
    feature1: defaultReducer,
    feature2: defaultReducer
} as ActionReducerMap<ApplicationState>;

export function getInitialState() {
    return {
        feature1: feature1.initialState,
        feature2: feature2.initialState
    } as ApplicationState;
}

export interface ApplicationState {
    feature1: feature1.Feature1State;
    feature2: feature2.Feature2State;
}

It appears as the features are lazy loaded in the reducers are correctly overwritten

@jayoungers thanks a lot! Your code give me another way of looking at it and understanding the how and why. Like the solution of defaultReducer as well 馃憤

So for Ionic (2+) it seems just having StoreModule.forRoot(..) in the app.module.ts file is enough, no need to repeat it in the individual pages. Still much to learn...

Thanks for sharing, all!

@ahoebeke glad it was helpful. Since I didn't get any responses I figured it was ok to go that route, so I posted a short article last week on it in case anyone else runs into the situation:

https://medium.com/youngers-consulting/ngrx-tips-part-1-module-setup-with-lazy-loading-5dc8994b5a2d

Was this page helpful?
0 / 5 - 0 ratings