Angular CLI schematics for generating NgRx state management code including actions, reducers, effects, selectors, and feature modules.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
NgRx reducer generation schematic that creates type-safe reducer functions using createReducer and on() functions to handle state updates in response to dispatched actions. Reducers are pure functions that specify how the application's state changes in response to actions.
Generates NgRx reducer functions with proper state management and action handling.
# Basic reducer generation
ng generate @ngrx/schematics:reducer User
# Reducer with creator functions
ng generate @ngrx/schematics:reducer User --creators
# Feature reducer setup
ng generate @ngrx/schematics:reducer User --feature/**
* Reducer schematic configuration interface
*/
interface ReducerSchema {
/** Name of the reducer (typically entity or feature name) */
name: string;
/** Path where reducer files should be generated */
path?: string;
/** Angular project to target */
project?: string;
/** Generate files without creating a folder */
flat?: boolean;
/** Group reducer files within folders */
group?: boolean;
/** Path to existing reducers file for feature setup */
reducers?: string;
/** Generate as feature reducer */
feature?: boolean;
/** Use creator functions (createReducer) */
creators?: boolean;
}Creates reducer functions using NgRx's createReducer and on() functions with proper state typing.
// Generated reducer with state interface
import { createReducer, on } from '@ngrx/store';
import * as UserActions from './user.actions';
export interface UserState {
users: User[];
loading: boolean;
error: string | null;
selectedUserId: string | null;
}
export const initialState: UserState = {
users: [],
loading: false,
error: null,
selectedUserId: null
};
export const userReducer = createReducer(
initialState,
on(UserActions.loadUsers, (state) => ({
...state,
loading: true,
error: null
})),
on(UserActions.loadUsersSuccess, (state, { users }) => ({
...state,
users,
loading: false,
error: null
})),
on(UserActions.loadUsersFailure, (state, { error }) => ({
...state,
loading: false,
error
})),
on(UserActions.selectUser, (state, { userId }) => ({
...state,
selectedUserId: userId
})),
on(UserActions.clearUsers, (state) => ({
...state,
users: [],
selectedUserId: null
}))
);Usage Examples:
# Generate user reducer
ng generate @ngrx/schematics:reducer User --creators
# Generate product reducer with custom path
ng generate @ngrx/schematics:reducer Product --path=src/app/catalog --creatorsThe schematic generates comprehensive state interfaces with proper typing:
/**
* Generated state interface with common patterns
*/
interface GeneratedState {
/** Collection of entities */
entities: Entity[];
/** Loading state indicator */
loading: boolean;
/** Error message if any */
error: string | null;
/** Currently selected entity ID */
selectedId: string | null;
/** Additional filters or search criteria */
filters?: FilterCriteria;
/** Pagination information */
pagination?: PaginationState;
}
interface PaginationState {
page: number;
pageSize: number;
total: number;
}
interface FilterCriteria {
searchTerm?: string;
status?: string;
dateRange?: DateRange;
}When using --feature flag, the schematic integrates with existing reducer files:
// Generated feature reducer integration
import { ActionReducerMap, MetaReducer } from '@ngrx/store';
import * as fromUser from './user.reducer';
import * as fromProduct from './product.reducer';
export interface AppState {
user: fromUser.UserState;
product: fromProduct.ProductState;
}
export const reducers: ActionReducerMap<AppState> = {
user: fromUser.userReducer,
product: fromProduct.productReducer
};
export const metaReducers: MetaReducer<AppState>[] = [];Usage Examples:
# Add to existing feature reducers
ng generate @ngrx/schematics:reducer Order --feature --reducers=src/app/state/index.ts
# Create feature reducer with grouping
ng generate @ngrx/schematics:reducer Inventory --feature --groupThe reducer includes common action handling patterns:
// CRUD operation handlers
export const entityReducer = createReducer(
initialState,
// Load operations
on(EntityActions.loadEntities, (state) => ({
...state,
loading: true,
error: null
})),
on(EntityActions.loadEntitiesSuccess, (state, { entities }) => ({
...state,
entities,
loading: false
})),
on(EntityActions.loadEntitiesFailure, (state, { error }) => ({
...state,
loading: false,
error
})),
// Create operations
on(EntityActions.createEntitySuccess, (state, { entity }) => ({
...state,
entities: [...state.entities, entity]
})),
// Update operations
on(EntityActions.updateEntitySuccess, (state, { entity }) => ({
...state,
entities: state.entities.map(e =>
e.id === entity.id ? entity : e
)
})),
// Delete operations
on(EntityActions.deleteEntitySuccess, (state, { id }) => ({
...state,
entities: state.entities.filter(e => e.id !== id)
})),
// Selection operations
on(EntityActions.selectEntity, (state, { id }) => ({
...state,
selectedId: id
})),
on(EntityActions.clearSelection, (state) => ({
...state,
selectedId: null
}))
);The generated reducer ensures immutable state updates following NgRx best practices:
/**
* Immutable state update patterns used in generated reducers
*/
interface StateUpdatePatterns {
/** Replace array items */
replaceInArray: <T>(array: T[], item: T, matcher: (item: T) => boolean) => T[];
/** Add to array */
addToArray: <T>(array: T[], item: T) => T[];
/** Remove from array */
removeFromArray: <T>(array: T[], matcher: (item: T) => boolean) => T[];
/** Update nested object */
updateNested: <T>(state: T, path: string[], value: any) => T;
}Comprehensive error handling patterns are included:
// Error handling in reducers
on(EntityActions.loadEntitiesFailure, (state, { error }) => ({
...state,
loading: false,
error: typeof error === 'string' ? error : error.message || 'Unknown error'
})),
on(EntityActions.createEntityFailure, (state, { error }) => ({
...state,
loading: false,
error: `Failed to create entity: ${error}`
})),
// Clear errors on new operations
on(EntityActions.loadEntities, EntityActions.createEntity, (state) => ({
...state,
error: null
}))The generated reducer is fully testable with proper isolation:
// Generated reducer tests
describe('UserReducer', () => {
describe('unknown action', () => {
it('should return the previous state', () => {
const action = {} as any;
const result = userReducer(initialState, action);
expect(result).toBe(initialState);
});
});
describe('loadUsers action', () => {
it('should set loading to true', () => {
const action = UserActions.loadUsers();
const result = userReducer(initialState, action);
expect(result.loading).toBe(true);
expect(result.error).toBe(null);
});
});
describe('loadUsersSuccess action', () => {
it('should update users and set loading to false', () => {
const users = [{ id: '1', name: 'John' }];
const action = UserActions.loadUsersSuccess({ users });
const result = userReducer(initialState, action);
expect(result.users).toEqual(users);
expect(result.loading).toBe(false);
});
});
});Full TypeScript support with strict typing:
/**
* Type-safe reducer function signature
*/
function createTypedReducer<T>(
initialState: T,
...ons: On<T>[]
): ActionReducer<T>;
/**
* Action reducer map for feature states
*/
interface ActionReducerMap<T> {
[key: string]: ActionReducer<T[keyof T]>;
}The generated reducer includes performance considerations:
// Optimized state updates
on(EntityActions.updateEntitySuccess, (state, { entity }) => {
const index = state.entities.findIndex(e => e.id === entity.id);
if (index === -1) return state;
const entities = [...state.entities];
entities[index] = entity;
return {
...state,
entities
};
});
// Conditional updates to prevent unnecessary renders
on(EntityActions.selectEntity, (state, { id }) =>
state.selectedId === id ? state : { ...state, selectedId: id }
);