Angular ngrx-store: How to share store among modules (main module / feature module)
Asked Answered
S

3

12

I have two angular modules: main and feature:

Main / root module:

@NgModule({
    imports: [
        StoreModule.forRoot({router: routerReducer}),
        EffectsModule.forRoot([...]),
        ...
    ],
    declarations: [],
    ...
})
export class AppModule {}

Feature module:

@NgModule({
    imports: [
        StoreModule.forFeature('feature', {someValue: someValueReducer}),
        EffectsModule.forFeature([...]),
        ...
    ],
    declarations: [],
    ...
})
export class FeatureModule {}

I need to access 'feature' slice of data in main module to conditionally display / activate application tab based on data stored in feature module.

(In other words I need a global / shared piece of state accessible by all modules main and any feature module.)

  1. Is it possible to do that?
  2. Is it considered as good practise?

Currently I can't do that since there is no feature state in main AppState:

export interface AppState {
    router: RouterReducerState<RouterStateUrl>;
}

And typescript indicates error this.store.select('feature') since there is no feature key of main store AppState.

Using selector: this.store.select(selectFeatureValue) I am getting runtime error:

export const selectFeatureModule = createFeatureSelector<FeatureState>('feature');
export const selectFeatureValue = createSelector(selectFeatureModule ,
(state: FeatureState) => state.someValue);

I am getting error in main AppComponent:

TypeError: Cannot read property 'someValue' of undefined

Schlenger answered 31/10, 2017 at 6:51 Comment(1)
setTimeout(() => this.store.select(selectFeatureValue) .subscribe(console.log.bind(console)), 1000); will get rid of the runtime error, but I would prefer to get rid of the setTimeoutSchlenger
S
11

In my opinion, that pretty much defeats the purpose of both: lazy loaded modules and lazy loaded stores.

You should probably rethink your design. Its usualy vice versa, that you need main state in your feature module and that's fine since you can't have feature module active without main one but vice versa is a bit odd.

Now, that being said, the main idea of lazy loaded store is that you don't have feature store keys in your main store but they are added afterwards, during the application lifetime as they are needed. So, what you can do is import your feature reducer into main module component where you need it and select from feature reducer directly. But again, its a question if that's something one would want to do. I wouldn't.

I would rethink the design and things i need in main module i would put into main reducer/store.

Stringer answered 31/10, 2017 at 7:20 Comment(12)
Thanks for you response. Considering the opposite way - feature module has access to main module store, what would be the syntax to acces the main store slice from feature module given that feature module has its own type FeatureState?Schlenger
I could dispatch an action in main module and feature store reducer could receive/handle this action and use the payload to save its own copy (similar to main store reducer).Schlenger
metareducers are what you might be looking for. for example: netbasal.com/…Stringer
Thinking about this one more: If the main module has a (module) dependency on feature module, I can not share the state in the main module - in this case the feature module would require to have a (module) dependency on main module also creating a circular dependency.Schlenger
if your main module have dependency on feature module then your feature module is not a lazy loaded one. although, that is not necessarily related to lazy loaded parts of store. but in general, you want to have lazy loaded reducers to follow lazy loaded modules so you dont have circular dependencies.Stringer
Isn't this what the ngrx sample app is doing? The container component at github.com/ngrx/platform/blob/master/example-app/app/core/… from core.module is accessing authState, from another feature module, auth.module. This is a case of sharing state between two feature modules, but I don't think it's much different from the original question in this thread. Is this aspect of the sample app an example of bad design you are describing? Not intending to debate good/bad design, just trying to understand your advice and the sample app.Impignorate
"Its usually vice versa, that you need main state in your feature module and that's fine" no it's not. You are thereby breaking the dependency inversion principle.Julijulia
@Julijulia are you sure? can you elaborate? for example, if you keep logged user or some app preferences in main state, why is using that info in feature modules breaking inversion of control principle?Stringer
@deezg Now your feature module knows about the main state. What if you use that module in a different app? Now your feature module depends on the main state and the main state on the feature module...Julijulia
@Julijulia i am not sure thats correct. maybe you could start by quoting dependency inversion principle and point to the part of it thats violated? /i never said main module depends on feature module; thats clear problem. i said feature module depends on main one (if needed). not ideal, but still i dont see dependency inversion principle violated.Stringer
@deezg The exact opposite is true. Main modules should depend on feature modules. Feature modules should not depend on the Main module. Imagine you want to use the feature modules in a different app. You can't unless you also update the other, new app to have the same state at the previous app.Julijulia
@Julijulia can you please quote dependency inversion principle that states that?Stringer
M
10

I think, this is a valid question at least partly. Imagine the following structure:

appState
├── coreState
│
├── lazyModuleSate1
│   ├── subStateA
│   └── subStateB
│
└── lazyModuleSate2
    ├── subStateA
    └── subStateB
  • coreState may hold common info about user, session...

  • Reducers of lazy modules may need to access such common state while processing actions.

    BUT

  • Reducer(s) of the core module see only coreState.

  • Reducers of lazy modules (defined with ActionReducerMap) see only their own subState. It is nice and clean for most of the cases, but sometimes, actions should be processed on condition of coreState. There would be no problem, coreState is always in the store.

  • Metareducers defined in the lazy modules see ONLY their own lazyModuleState. It is still useful, to handle interconnections between subStates, but they do not help with coreState - lazyModuleSate relations.

  • Only global metareducers defined at app level see the coreState. It is possible to handel actions here, although very ugly.

Municipal answered 22/12, 2018 at 3:15 Comment(0)
S
5

My solution:

  • extracted extra feature module called core. I have refactored all shared global / state required by other modules into core.
  • added a dependency from all modules using shared state on core (no circullar dependency)
  • using selectors from core module there is no issue with typescript indicating there is no such key in store
Schlenger answered 17/1, 2018 at 16:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.