paztek
paztek

Reputation: 406

How to nest states of lazy-loaded feature modules with @ngrx/store and @ngrx/entity?

I've been struggling for 2 days to compose states of a featureA ("scenarios") module and a nested featureB ("intents") module.

Here is the desired structure of my state (structure 1):

{
  authentication: { ... },
  router: { ... },
  scenarios: {
    resources: { ids: { ... }, entities: { ... } },
    intents: {
      resources: { ids: { ... }, entities: { ... } }
    }
  }
}

I'd be happy also with (structure 2):

{
  authentication: { ... },
  router: { ... },
  scenarios: { ids: { ... }, entities: { ... } },
  intents: { ids: { ... }, entities: { ... } },
}

Although it doesn't reflect the structure of my modules.

The problem with structure 1 is that scenarios/reducers/index.ts exposes a ActionReducerMap, scenarios/modules/intents/reducers/index.ts also exposes a ActionReducerMap and I don't know how to compose them. Everything I tried doesn't even compile because of type conflicts.

The problem with structure 2 is that the intents part of the state keeps being dropped out of the state because of the IntentsModule not being loaded at first page load and other reducers of the app doing their job.

Here is the code:

/scenarios/scenarios.module.ts:

import { reducers, getInitialState } from './reducers';

@NgModule({
  imports: [
    ...,

    ScenariosRoutingModule,

    StoreModule.forFeature('scenarios', reducers, { initialState: getInitialState }),
    EffectsModule.forFeature([ScenariosEffects]),
  ],
})
export class ScenariosModule {}

/scenarios/reducers/index.ts:

import * as fromRoot from '../../../app.reducers';
import * as fromScenarios from './scenarios.reducers';
import * as fromIntents from '../modules/intents/reducers';

export interface ScenariosState {
  resources: fromScenarios.State;
  intents: fromIntents.IntentsState;
}

export interface State extends fromRoot.State {
  scenarios: ScenariosState;
}

export const reducers: ActionReducerMap<ScenariosState> = {
  resources: fromScenarios.reducer,
  intents: fromIntents.reducers, <-- This is the part with types conflicts
};

/scenarios/modules/intents/intents.module:

import { reducers, getInitialState } from './reducers';

@NgModule({
  imports: [
    ...,

    IntentsRoutingModule,

    StoreModule.forFeature('intents', reducers, { initialState: getInitialState }), <-- Also this part seems to add 'intents' as a top-level property of state
    EffectsModule.forFeature([IntentsEffects]),
  ],
})
export class IntentsModule {}

/scenarios/modules/intents/reducers/index.ts:

import * as fromIntents from './intents.reducers';

export interface IntentsState {
  resources: fromIntents.State;
}

export interface State {
  intents: IntentsState;
}

export const reducers: ActionReducerMap<IntentsState> = {
  resources: fromIntents.reducer,
};

export function getInitialState(): IntentsState {
  return {
    resources: fromIntents.initialState,
  };
}

Upvotes: 2

Views: 1727

Answers (2)

user7151805
user7151805

Reputation:

  • import and export the structure 2 IntentsModule in the root AppModule.
  • import the ScenariosModule after the IntentsModule in the root AppModule.

Do not nest various IntentsModules under the ScenariosModules. Instead create separation and flatten your project structure as much as possible this is inherently required by structure 2.

I've found that a good approach for most situations with the exception of Auth related features is to create two modules per feature to be lazily loaded together yet decoupling state-management from the user-interface yet these corresponding modules should share an API of the same shape i.e:

  • (FrameworkLayoutModule and FrameworkStoreModule) and (AuthModule).

Subsequently, reusable features may be lazily loaded by a SecureModule or a PublicModule as needed.

Upvotes: 2

Kliment Ru
Kliment Ru

Reputation: 2137

This problem with use 'intents' store in two modules. And NgRx can`t create 'intents' store twice.

solution 1

remove 'intents' from scenarios.module or from intents.module if it dont use there.

solution 2

move 'intents' store into StoreModule.forRoot or into scenarios and intents parent module.

Upvotes: 0

Related Questions