RxJS powered Redux state management for Angular applications
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Module configuration provides NgModule-based setup for traditional Angular applications using the StoreModule with forRoot and forFeature patterns for root and feature state management.
Main NgModule for configuring NgRx Store in module-based Angular applications.
/**
* NgModule for configuring NgRx Store
*/
class StoreModule {
/**
* Configure the root store with initial reducers and configuration
* @param reducers - Map of reducers or injection token for reducers
* @param config - Root store configuration options
* @returns ModuleWithProviders for StoreRootModule
*/
static forRoot<T, V extends Action = Action>(
reducers?: ActionReducerMap<T, V> | InjectionToken<ActionReducerMap<T, V>>,
config?: RootStoreConfig<T, V>
): ModuleWithProviders<StoreRootModule>;
/**
* Configure feature store with feature-specific reducers
* @param featureName - Name/key for the feature state slice
* @param reducers - Feature reducers map or single reducer
* @param config - Feature store configuration options
* @returns ModuleWithProviders for StoreFeatureModule
*/
static forFeature<T, V extends Action = Action>(
featureName: string,
reducers: ActionReducerMap<T, V> | InjectionToken<ActionReducerMap<T, V>>,
config?: StoreConfig<T, V> | InjectionToken<StoreConfig<T, V>>
): ModuleWithProviders<StoreFeatureModule>;
static forFeature<T, V extends Action = Action>(
featureName: string,
reducer: ActionReducer<T, V> | InjectionToken<ActionReducer<T, V>>,
config?: StoreConfig<T, V> | InjectionToken<StoreConfig<T, V>>
): ModuleWithProviders<StoreFeatureModule>;
static forFeature<T, V extends Action = Action>(
slice: FeatureSlice<T, V>
): ModuleWithProviders<StoreFeatureModule>;
}
/**
* Root store module implementation
*/
class StoreRootModule {}
/**
* Feature store module implementation
*/
class StoreFeatureModule implements OnDestroy {}Usage Examples:
import { NgModule } from "@angular/core";
import { StoreModule } from "@ngrx/store";
import { reducers, metaReducers } from "./app.reducers";
// Root module configuration
@NgModule({
imports: [
StoreModule.forRoot(reducers, {
metaReducers,
runtimeChecks: {
strictStateImmutability: true,
strictActionImmutability: true,
strictStateSerializability: true,
strictActionSerializability: true,
strictActionWithinNgZone: true,
strictActionTypeUniqueness: true
}
})
]
})
export class AppModule {}
// Feature module with reducer map
@NgModule({
imports: [
StoreModule.forFeature('users', {
list: userListReducer,
profile: userProfileReducer,
preferences: userPreferencesReducer
}, {
metaReducers: [userMetaReducer],
initialState: {
list: { entities: [], loading: false },
profile: null,
preferences: defaultPreferences
}
})
]
})
export class UserModule {}
// Feature module with single reducer
@NgModule({
imports: [
StoreModule.forFeature('orders', orderReducer, {
initialState: initialOrderState
})
]
})
export class OrderModule {}
// Feature module with FeatureSlice
@NgModule({
imports: [
StoreModule.forFeature({
name: 'products',
reducer: productReducer,
initialState: initialProductState
})
]
})
export class ProductModule {}/**
* Configuration options for root store
*/
interface RootStoreConfig<T, V extends Action = Action> {
/** Initial state for the entire application */
initialState?: InitialState<T>;
/** Meta-reducers applied to all reducers */
metaReducers?: MetaReducer<T, V>[];
/** Runtime validation checks configuration */
runtimeChecks?: Partial<RuntimeChecks>;
}
/**
* Runtime validation checks configuration
*/
interface RuntimeChecks {
/** Verifies if the state is serializable */
strictStateSerializability: boolean;
/** Verifies if the actions are serializable */
strictActionSerializability: boolean;
/** Verifies that the state isn't mutated */
strictStateImmutability: boolean;
/** Verifies that actions aren't mutated */
strictActionImmutability: boolean;
/** Verifies that actions are dispatched within NgZone */
strictActionWithinNgZone: boolean;
/** Verifies that action types are not registered more than once */
strictActionTypeUniqueness?: boolean;
}/**
* Configuration options for feature store
*/
interface StoreConfig<T, V extends Action = Action> {
/** Initial state for the feature */
initialState?: InitialState<T>;
/** Meta-reducers applied to feature reducers */
metaReducers?: MetaReducer<T, V>[];
}
/**
* Feature slice configuration
*/
interface FeatureSlice<T, V extends Action = Action> {
/** Name/key for the feature */
name: string;
/** Reducer function for the feature */
reducer: ActionReducer<T, V>;
/** Initial state for the feature */
initialState?: InitialState<T>;
/** Meta-reducers for the feature */
metaReducers?: MetaReducer<T, V>[];
}
/**
* Initial state configuration type
*/
type InitialState<T> = Partial<T> | TypeId<Partial<T>> | void;
type TypeId<T> = () => T;// Lazy-loaded feature module
@NgModule({
imports: [
CommonModule,
StoreModule.forFeature('billing', billingReducer, {
initialState: () => ({
// Dynamic initial state
invoices: [],
currentPlan: getCurrentUserPlan(),
settings: getUserBillingSettings()
})
})
]
})
export class BillingModule {}
// Route configuration with lazy loading
const routes: Routes = [
{
path: 'billing',
loadChildren: () => import('./billing/billing.module').then(m => m.BillingModule)
}
];import { InjectionToken } from "@angular/core";
// Injectable reducer configuration
export const USER_REDUCERS = new InjectionToken<ActionReducerMap<UserState>>('User Reducers', {
factory: () => ({
profile: userProfileReducer,
settings: userSettingsReducer,
notifications: userNotificationsReducer
})
});
export const USER_CONFIG = new InjectionToken<StoreConfig<UserState>>('User Config', {
factory: () => ({
initialState: {
profile: null,
settings: defaultUserSettings,
notifications: { enabled: true, preferences: {} }
},
metaReducers: environment.production ? [] : [storeFreeze]
})
});
// Module using injectable tokens
@NgModule({
imports: [
StoreModule.forFeature('users', USER_REDUCERS, USER_CONFIG)
],
providers: [
// Custom providers if needed
{ provide: USER_CONFIG, useFactory: createUserConfig, deps: [UserService] }
]
})
export class UserModule {}import { MetaReducer } from "@ngrx/store";
import { storeFreeze } from "ngrx-store-freeze";
import { storeLogger } from "./store-logger";
// Environment-specific meta-reducers
export const metaReducers: MetaReducer<AppState>[] = !environment.production
? [storeLogger, storeFreeze]
: [];
// Feature-specific meta-reducers
const userMetaReducers: MetaReducer<UserState>[] = [
// Audit trail for user actions
auditTrailMetaReducer,
// Validation for user state changes
userValidationMetaReducer
];
@NgModule({
imports: [
StoreModule.forRoot(reducers, { metaReducers }),
StoreModule.forFeature('users', userReducer, {
metaReducers: userMetaReducers
})
]
})
export class AppModule {}// Environment-specific configurations
const developmentConfig: RootStoreConfig<AppState> = {
runtimeChecks: {
strictStateImmutability: true,
strictActionImmutability: true,
strictStateSerializability: true,
strictActionSerializability: true,
strictActionWithinNgZone: true,
strictActionTypeUniqueness: true
},
metaReducers: [storeLogger, storeFreeze]
};
const productionConfig: RootStoreConfig<AppState> = {
runtimeChecks: {
strictStateImmutability: false,
strictActionImmutability: false,
strictStateSerializability: false,
strictActionSerializability: false,
strictActionWithinNgZone: false,
strictActionTypeUniqueness: false
},
metaReducers: []
};
@NgModule({
imports: [
StoreModule.forRoot(
reducers,
environment.production ? productionConfig : developmentConfig
)
]
})
export class AppModule {}import { OnDestroy } from "@angular/core";
import { Store } from "@ngrx/store";
@NgModule({
imports: [
StoreModule.forFeature('analytics', analyticsReducer)
]
})
export class AnalyticsModule implements OnDestroy {
constructor(private store: Store) {
// Initialize feature on module load
this.store.dispatch(initializeAnalytics());
}
ngOnDestroy() {
// Cleanup when module is destroyed
this.store.dispatch(cleanupAnalytics());
}
}import { Injector } from "@angular/core";
import { Store } from "@ngrx/store";
export class DynamicFeatureService {
constructor(
private store: Store,
private injector: Injector
) {}
loadFeature(featureName: string, reducer: ActionReducer<any>) {
// Dynamically add feature reducer
this.store.addReducer(featureName, reducer);
// Initialize feature state
this.store.dispatch(initializeFeature({ featureName }));
}
unloadFeature(featureName: string) {
// Cleanup feature state
this.store.dispatch(cleanupFeature({ featureName }));
// Remove feature reducer
this.store.removeReducer(featureName);
}
}import { Router } from "@angular/router";
import { Store } from "@ngrx/store";
// Route-based feature loading
const routes: Routes = [
{
path: 'dashboard',
loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule),
resolve: {
// Preload dashboard data
dashboardData: (route, state) => {
const store = inject(Store);
store.dispatch(loadDashboardData());
return store.select(selectDashboardLoaded).pipe(
filter(loaded => loaded),
take(1)
);
}
}
}
];