简体   繁体   中英

How to register ngrx sub features (StoreModule.forFeature()) for my angular app?

Following is my angular app structure

app.module.ts
 -- StoreModule.forRoot()
    mainboard.module.ts
        --StoreModule.forFeature('mainboard', reducer)

            subFeature1.module.ts
            subFeature2.module.ts
            subFeature3.module.ts

    dashboard1.module.ts
        --StoreModule.forFeature('dashboard1', reducer)

            subFeature1.module.ts
            subFeature2.module.ts
            subFeature3.module.ts
            subFeature4.module.ts
            subFeature5.module.ts

    dashboard2.module.ts
        --StoreModule.forFeature('dashboard2', reducer)

            subFeature1.module.ts
            subFeature2.module.ts

    dashboard3.module.ts
        --StoreModule.forFeature('dashboard3', reducer)

            subFeature1.module.ts
            subFeature2.module.ts
            subFeature3.module.ts
            subFeature4.module.ts

    dashboard4.module.ts
        --StoreModule.forFeature('dashboard4', reducer)

            subFeature1.module.ts
            subFeature2.module.ts


So the requirement is to have store for subfeatures.

Like this:

app.module.ts
 -- StoreModule.forRoot()
    mainboard.module.ts
        --StoreModule.forFeature('mainboard', reducer)

            subFeature1.module.ts
              --StoreModule.forFeature('subFeature1', reducer)

            subFeature2.module.ts
              --StoreModule.forFeature('subFeature1', reducer)
            subFeature3.module.ts
              --StoreModule.forFeature('subFeature1', reducer)


...


HOW CAN I ACHIEIVE SUCH A HIERARCHY FOR MY NGRX/STORE?

app.module is there all the dashboards are placed.

dashboard-x.modules are the places where all the navs/ items are placed.

and inside each dashboards sub features are included.

So my question is about how to register subFeatures with StoreModule.forFeature()?

Or do I need to make stores for each dashboards (StoreModule.forRoot()) and then StoreModule.forFeature() for each subFeatures? (if so then how may I be able to register it inside the app's StoreModule.forRoot())

NOTE

Currently I'm registering all of the subfeatures as forFeature.('subfeturename', reducer)

But the problem with this is that when I have a look at my state tree (Redux devtools) it is not following the above store tree structure. Since all the sub features are getting registered as forFeature() all of them are showing as properties (ie, they are not getting nested as expected). What I want instead is that I want to have them nested inside my state tree.

So if I have a look at my state tree I can see nested state Like this:

app
    mainboard(open)
          subFeature1
          subFeature2
          subFeature3

    dashboard1(closed)

    dashboard2(open)
          subFeature1
          subFeature2

    dashboard3(closed)

    dashboard4(closed)

//open and closed means expand and collapsed tree

Remember, each level (app > dashboard1 > subfeature) has different properties which needs to be managed by stores. So a store is necessary for each level.

There are a lot of ways to do it. Below will be only my option based on my experience.

First thing to note - forFeature doesn't create an independent store context. It simply connects new reducers and effects on a later stage after a call of forRoot and these effects and reducers will be getting all actions from the whole application, so if you send updateMyLazyFeature(123) in one module - all modules that connected their reducers via forFeature will get this action and reduce it, and it's very easy to get data collisions, when you think that only your module will be updated but in reality it affects other parts of the application too.

When you want to split your store on independent features you need to review how a store feature is used. If the feature is used in more than 1 module, angular will put it in the shared file after build and it doesn't have any benifit compare to forRoot .

if the feature is used in several modules - the best place would be to keep it in forRoot . If you use the feature only in a particular module and it's so tied that there's zero chance to use it anywhere else - then you can put it as a separate store feature.

One more case - if you implement a library with ngrx store, then you need to use forFeature because you can't access forRoot from your lib.

An example, we have a dashboard application and users have to login. It means that we should have access to user's data in several components, in this case the right place would be to put the feature in forRoot .

Apart from that, we have a lazy registration module and a user can use an invitation link to prefill the form. Because the prefilled data won't be used anywhere else and the registration module is seldomly used too, we can implement the feature as forFeature in the registration module. If user isn't going to register during his session - angular won't be going to load the module and the feature at all.

Also in the flux pattern it's important to keep data flat. The main problem flux and ngrx solve is data consistency and the easiest way to achieve it is to keep it flat.

Despite modules are nested like app -> mainboard -> subFeature1 etc, you still can add them to the top level of the store so they'll be stored on the same level as other reducers.

store
    users
    mainboard
    dashboard2
    subFeature1
    subFeature2
    subFeature3

There's no need to make it like

store
    users
    mainboard
        subFeature1
        subFeature2
        subFeature3
    dashboard2
        subFeature1
        subFeature2
        subFeature3

It will bring complexity later when behavior of selectors, reducers and actions isn't so clear and predictable. With the flat structure you can move your feature to another project with ease.

Based on this info I would vote for the flat structure and would use

StoreModule.forFeature('logsControls', logsControls.reducer);

instead of

StoreModule.forFeature('dashboard1', {
  logsControls: logsControls.reducer,
});
StoreModule.forFeature('dashboard2', {
  logsControls: logsControls.reducer,
});

Because in the second case an action will update both dashboard1 and dashboard2 and requires a technique like correlationId to affect only one feature, despite it looks like that dashboard1 and dashboard2 are different features and work independently, that's a wrong guess.

When you're registering a feature , you have 2 options:

StoreModule.forFeature('featName', reducer)

or

StoreModule.forFeature('featName', { subFeat1: subFeat1Reducer, subFeat2: subFeat2Reducer })

So I think you can achieve what you're looking for by using the second approach.

so this can be a little confusing since its a bit black boxed but essentially in each of you modules that you lazy load in you will add the forfeature method just as you are doing.

Core or Root AppState:

export interface AppState {
  layout: LayoutState;
  ...etc
}

export const appReducer: ActionReducerMap<AppState> = {
  layout:  layoutReducer,
  ...etc
};

add this to imports in your core.module or app.module

StoreModule.forRoot(appReducer),

a lazy loaded or othe rimported module will have a completely decoupled store that gets appended to this main appstate when the module is loaded

export interface FeatureAppState {
  featureSettings: FeatureState;
  ...etc
};

export const FeatureAppReducer: ActionReducerMap<FeatureAppState> = {
  featureSettings: FeatureReducer,
  ...etc
};

and in your module for that feature you import the following

StoreModule.forFeature('featureApp', FeatureAppReducer)

AND THATS IT! it won't be hierarchical in nature though you will want to use selectors in your stores so for example to get layout in the parent state

export const selectLayoutState = createSelector((state: AppState) => state, (state: AppState) => state.layout);

export const selectLayoutMainMenu = createSelector(selectLayoutState, state => state.mainMenu);

would get you the mainmenu from the layout store from core and in your feature stores to get the feature state

export const getFeatureState = 
createSelector(createFeatureSelector('featureApp'), (state: FeatureAppState) 
=> state.featureSettings); 

remember that angular works off the concept of reusability so things in stat tend to be very flat so you could import subfeature1 under mainboard and dashboard2 and they will utilize the same store.

If you need to manage states tied to different parents you may try using something like ngrx entity the idea being you could store each state with a unique id for that parent feature or pass a string into the forFeature in your feature module so for example using forChild on the import you could pass in an argument and pass the argument into the StoreModules.forFeature import. I'm not sure that would work but you could try it. In the end I think this is a bad design though and there are better ways of doing it.

but your not going to get a new state using forFeature for each time you import the module thats just not how it works.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM