jhen
jhen

Reputation: 1252

@ngrx/store combine multiple reducers from feature module

I am currently working on a simple test app to learn more about the @ngrx/store. I have a module called TrainingModule which should store some exercises and more information. The code works, but i try to improve here. What i currently have is my feature module that looks like this:

@NgModule({
  imports: [
    CommonModule,
    TrainingRoutingModule,
    StoreModule.forFeature('exercises', exerciseReducer)
  ],
  declarations: [
    TrainingDashboardComponent,
    TrainingCoreComponent,
    TrainingNavComponent,
    TrainingPlanComponent,
    ExerciseOverviewComponent,
    ExerciseListComponent]
})
export class TrainingModule {
}

and my reducer that looks like that:

export interface ExerciseState {
  exercises: IExercise[];
}

export interface State extends fromRoot.State {
  'exercises': ExerciseState;
}

export const initialState: ExerciseState = {
  exercises: [
    {id: 1, name: 'Exc 1'},
    {id: 2, name: 'Exc 2'}
  ]
};

export function exerciseReducer(state: ExerciseState = initialState, action: any): ExerciseState {
  switch (action.type) {
    default:
      return state;
  }
}

export const getExerciseState = createFeatureSelector<ExerciseState>('exercises');
export const getExercises = createSelector(getExerciseState, state => state.exercises);

So far so good. In my template I select my exercise from the store with that

exercise$: Observable<IExercise[]>;

  constructor(private store: Store<State>) { }

  ngOnInit() {
    this.exercise$ = this.store.select(getExercises);
  }

So what i want to do now combine my reducers so that i don´t have to add every reducer like this

StoreModule.forFeature('exercises', exerciseReducer);
StoreModule.forFeature('sample', sampleReducer);
StoreModule.forFeature('sample1', sampleReducer1);

In all my modules. I tried to collect all reducers with

export const trainingReducers = {
  'exercise': exerciseReducer
};

and

StoreModule.forFeature('training', trainingReducers)

But that gave me a Cannot read property 'exercises' of undefined error message in the console. Maybe someone can help me understand, how do i collect all reducers from the feature modul and create a correct selector for that.

Upvotes: 19

Views: 31670

Answers (3)

klivladimir
klivladimir

Reputation: 256

With new feature createFeature you can do it easier.

  1. Add feature to AppState interface
export interface AppState {
  [fromFeature.name]: fromFeature.FeatureStateInterface
}

// Here you can register main reducers
// Make it Partial!!!
export const reducers: Partial<ActionReducerMap<AppState>> = {
  user: fromUser.reducer,
  [fromFeature.name]: fromFeature.reducer //<-- no need to register feature reducer
}
// this ActionReducerMap you need to pass to StoreModule.forRoot()
  1. Create some feature inside module. For example
export interface State {
  key: string;
}

export const initialState: State = {
  key: ''
};

export const feature = createFeature({
  name: 'module',
  reducer: createReducer(
    initialState
  )
});

export const {
  name,
  reducer
} = feature;
  1. Combine all your reducers from module
import * as fromGreatFeature from './store/great-feature/great-feature.reducer';
import * as fromEntity from './store/entity/entity.reducer';
import * as fromModule from './store/module/module.reducer';


export interface ScheduleState {
  module: fromModule.State; // <- from example
  entity: fromEntity.State;
  greatFeature: fromGreatFeature.State;
}


export const reducers = combineReducers({
  module: fromModule.reducer, // <- from example 
  entity: fromEntity.reducer,
  greatFeature: fromGreatFeature.reducer
});
  1. Make separate file and define store module or make it inside main module file
export const yourFeature = createFeature<AppState>({
  name: 'yourFeature', // name from interface
  reducer: reducers // combined reducers
});

export const {
  name,
  reducer
} = yourFeature;-----------------
                                |
                                |
@NgModule({                     |
  imports: [                    |
    StoreModule.forFeature(yourFeature),
    EffectsModule.forFeature([Effects, Effects, Effects])
  ]
})
export class LegendaryStoreModule {}
@NgModule({
  declarations: [
    SomeComponent
  ],
  imports: [
    SharedModule,
    LegendaryRoutingModule,
    LegendaryStoreModule, // <- your store module
  ]
})
export class YourLegendaryModule {}
  1. And selectors. Function createFeature automatically generate for you selectors, but you need crate mainFeature selector
export const mainFeatureSelector = createFeatureSelector<AppState, SchedulesFeatureState>(scheduleFeature.name)

    export const getSomeThing = createSelector(
  mainFeatureSelector, // <- main feature state
  fromModule.selectSome // <- nested state
);

Upvotes: 3

elobuho
elobuho

Reputation: 131

Your setup is almost correct.

In the createFeatureSelector function you declare a feature key in the root of the store, 'exercises' in the example. So, you are going to select store.exercises.exercises.id for example and feature selector is just a shortcut to store.exercises.

However, in the StoreModule.forFeature('training', trainingReducers) call you defined 'training' as the root key for your feature module.

The correct setup could look like this:

export const featureReducersMap = {
  exercise: exerciseReducer,
  anyOtherKey: anyOtherReducer
};
StoreModule.forFeature('myFeatureModule', featureReducersMap);
export interface FeatureState{
  exercise: string;
  anyOtherKey: number;
}

Then write selectors like:

export const featureSelector = createFeatureSelector<FeatureState>('myFeatureModule');

export const exerciseSelector = createSelector(
  featureSelector,
  (state: FeatureState) => state.exercise
);

It is recommended to store the feature key in the variable instead of hardcoding it.

Upvotes: 3

wheeler
wheeler

Reputation: 687

I can give you an example how I did it. I used an index.ts to bundle all other reducers from within the module like this:

module/reducers/index.ts

import * as fromRoot from '../../../reducers';
import * as fromSearch from './search';
import * as fromUserDetail from './user-detail';
import * as fromDetailBase from './base';


export interface UserModuleState {
  search: fromSearch.State;  
  detail: fromUserDetail.State;
  detailBase: fromDetailBase.State;
}

export interface State extends fromRoot.State {
    userModule: UserModuleState;    
}

export const reducers = {    
    search: fromSearch.reducer,
    detail: fromUserDetail.reducer,
    detailBase : fromDetailBase.reducer
};

export const selectUserModuleState = createFeatureSelector<UserModuleState>('userModule');


export const selectSearchState = createSelector(
    selectUserModuleState, (state: UserModuleState) => state.search
);
export const getSearchLoading = createSelector(selectSearchState, fromSearch.getLoading);
export const getSearchEntities = createSelector(selectSearchState, fromSearch.getEntities);

module/user.module.ts

import { reducers } from './reducers';
@NgModule({
    imports: [
        ...
        StoreModule.forFeature('userModule', reducers)
    ],
     ...
})
export default class UserModule { }

Upvotes: 43

Related Questions