Recently, we started leveraging NGRX in one of our bigger Angular projects. Hands down, NGRX is not the easiest, but undoubtedly one of the most powerful state management libraries out there. In this blog post, I want to tackle an issue that you might have encountered as well when working with reducers in NGRX. That is, injecting services into reducers, so that the initial state can be non-statically defined or set.
What this blog post is not about
NGRX’s documentation is great and there are many samples and blog posts out there that help you getting started — even most of the advanced topics are covered:
- NGRX Docs, Blog and Getting started
- Tomas Trajan writes tremendously well-written articles about architecting Angular applications using (not only) NGRX
What we are going to build
Think of a quite straightforward reducer for a state. This one below controls the theme of a simple application in a state:
import { createReducer, on } from '@ngrx/store';
import { changeTheme, changeAutoNightMode } from './settings.actions';
import { Theme } from './theme.model';
export const FEATURE_SETTINGS_KEY = 'settings';
export interface State {
theme: Theme;
autoNightMode: boolean;
nightTheme: Theme;
}
export const initialState: State = {
theme: Theme.Light,
nightTheme: Theme.Dark,
autoNightMode: false,
};
export const settingsReducer = createReducer(
initialState,
on(
changeTheme,
changeAutoNightMode,
(state, action): State => ({
...state,
...action,
})
)
);
Now, how can we manage to set the initial state if it depends on a service? Think of this:
- Set the initial state by reading the most recent state the application had from the local storage (if available)
- Set the initial state differently, depending on the currently logged-in user
- Set the initial state to a value that comes from a server
The solution: Inject a service into the reducer and use a factory
NGRX’s docs state that there is a simple way of injecting dependencies into reducers. That is, use a factory, create a StoreConfig from it and use an InjectionToken like below to tell Angular which factory to use when retrieving a new StoreConfig for a feature store:
import { CommonModule } from '@angular/common';
import { InjectionToken, NgModule } from '@angular/core';
import { EffectsModule } from '@ngrx/effects';
import { StoreConfig, StoreModule } from '@ngrx/store';
import { LocalStorageService } from 'src/app/core/local-storage/local-storage.service';
import { SettingsEffects } from './settings.effects';
import * as fromSettings from './settings.reducer';
export const SETTINGS_CONFIG_TOKEN = new InjectionToken<
StoreConfig<fromSettings.State>
>(fromSettings.FEATURE_SETTINGS_KEY);
export function settingsConfigFactory(
localStorageService: LocalStorageService
): StoreConfig<fromSettings.State> {
return {
initialState:
localStorageService.getItem(fromSettings.FEATURE_SETTINGS_KEY) ??
fromSettings.initialState,
};
}
@NgModule({
imports: [
CommonModule,
StoreModule.forFeature(
fromSettings.FEATURE_SETTINGS_KEY,
fromSettings.settingsReducer,
SETTINGS_CONFIG_TOKEN
),
EffectsModule.forFeature([SettingsEffects]),
],
declarations: [],
providers: [
{
provide: SETTINGS_CONFIG_TOKEN,
deps: [LocalStorageService],
useFactory: settingsConfigFactory,
},
],
})
export class SettingsStateModule {}
Here, I declared a new module that handles a part of the state of the app (all of this can be managed without feature states also).
The InjectionToken is now used within the providers section of the module to declare how the reducer is going to be built. The secret sauce is the factory as it returns a StoreConfig that holds an initial state.
For further read, head over to the official NGRX docs
.
Reach out if you have questions
If you have questions on how to manage feature state with NGRX and how to inject services into them, just head over to my GitHub profile
where you can find links to my social media. All of the shown code can be found in a little project here
.
Happy coding and thank you for coming around! 🎉