0
# Reducer Creators
1
2
Reducer creators provide a simplified, type-safe way to create reducers using the `on` function pattern instead of traditional switch statements. They automatically handle action type matching and provide full TypeScript support.
3
4
## Capabilities
5
6
### Create Reducer
7
8
Creates a reducer function using action handlers defined with the `on` function.
9
10
```typescript { .api }
11
/**
12
* Creates a reducer function to handle state transitions
13
* @param initialState - Initial state value when state is undefined
14
* @param ons - Action handlers created with the on() function
15
* @returns ActionReducer function for state management
16
*/
17
function createReducer<State>(
18
initialState: State,
19
...ons: ReducerTypes<State, any>[]
20
): ActionReducer<State>;
21
```
22
23
**Usage Examples:**
24
25
```typescript
26
import { createReducer, on } from "@ngrx/store";
27
import { increment, decrement, reset, setValue } from "./counter.actions";
28
29
interface CounterState {
30
count: number;
31
lastUpdated: number;
32
}
33
34
const initialState: CounterState = {
35
count: 0,
36
lastUpdated: Date.now()
37
};
38
39
// Create reducer with action handlers
40
export const counterReducer = createReducer(
41
initialState,
42
43
// Simple state update
44
on(increment, (state) => ({
45
...state,
46
count: state.count + 1,
47
lastUpdated: Date.now()
48
})),
49
50
on(decrement, (state) => ({
51
...state,
52
count: state.count - 1,
53
lastUpdated: Date.now()
54
})),
55
56
// Reset to initial state
57
on(reset, () => initialState),
58
59
// Handle action with payload
60
on(setValue, (state, { value }) => ({
61
...state,
62
count: value,
63
lastUpdated: Date.now()
64
})),
65
66
// Handle multiple actions with same logic
67
on(clearCounter, resetCounter, () => ({
68
...initialState,
69
lastUpdated: Date.now()
70
}))
71
);
72
```
73
74
### On Function
75
76
Associates action creators with their corresponding state change functions.
77
78
```typescript { .api }
79
/**
80
* Associates actions with a given state change function
81
* @param args - ActionCreator(s) followed by a state change function
82
* @returns Association of action types with state change function
83
*/
84
function on<State, Creators extends readonly ActionCreator[], InferredState = State>(
85
...args: [
86
...creators: Creators,
87
reducer: OnReducer<State extends infer S ? S : never, Creators, InferredState>
88
]
89
): ReducerTypes<unknown extends State ? InferredState : State, Creators>;
90
91
/**
92
* Specialized reducer that is aware of the Action type it needs to handle
93
*/
94
interface OnReducer<State, Creators extends readonly ActionCreator[], InferredState = State, ResultState = unknown extends State ? InferredState : State> {
95
(
96
state: unknown extends State ? InferredState : State,
97
action: ActionType<Creators[number]>
98
): ResultState;
99
}
100
101
/**
102
* Return type of the on() function containing the reducer and action types
103
*/
104
interface ReducerTypes<State, Creators extends readonly ActionCreator[]> {
105
reducer: OnReducer<State, Creators>;
106
types: ExtractActionTypes<Creators>;
107
}
108
```
109
110
**Usage Examples:**
111
112
```typescript
113
import { on } from "@ngrx/store";
114
import { loadUser, loadUserSuccess, loadUserFailure } from "./user.actions";
115
116
// Single action handler
117
const userLoadingHandler = on(loadUser, (state) => ({
118
...state,
119
loading: true,
120
error: null
121
}));
122
123
// Multiple actions with same handler
124
const userResetHandler = on(
125
resetUser,
126
clearUserData,
127
logoutUser,
128
(state) => ({
129
...state,
130
user: null,
131
loading: false,
132
error: null
133
})
134
);
135
136
// Action with payload
137
const userSuccessHandler = on(
138
loadUserSuccess,
139
(state, { user, timestamp }) => ({
140
...state,
141
user,
142
loading: false,
143
error: null,
144
lastUpdated: timestamp
145
})
146
);
147
148
// Error handling
149
const userErrorHandler = on(
150
loadUserFailure,
151
(state, { error }) => ({
152
...state,
153
loading: false,
154
error: error.message
155
})
156
);
157
158
// Use in reducer
159
export const userReducer = createReducer(
160
initialUserState,
161
userLoadingHandler,
162
userSuccessHandler,
163
userErrorHandler,
164
userResetHandler
165
);
166
```
167
168
## Core Type Definitions
169
170
### Action Reducer Types
171
172
```typescript { .api }
173
/**
174
* Function that takes an Action and a State, and returns a State
175
*/
176
interface ActionReducer<T, V extends Action = Action> {
177
(state: T | undefined, action: V): T;
178
}
179
180
/**
181
* Map of state keys to their corresponding reducers
182
*/
183
type ActionReducerMap<T, V extends Action = Action> = {
184
[p in keyof T]: ActionReducer<T[p], V>;
185
};
186
187
/**
188
* Factory for creating action reducers
189
*/
190
interface ActionReducerFactory<T, V extends Action = Action> {
191
(
192
reducerMap: ActionReducerMap<T, V>,
193
initialState?: InitialState<T>
194
): ActionReducer<T, V>;
195
}
196
197
/**
198
* Higher-order reducer that wraps other reducers
199
*/
200
type MetaReducer<T = any, V extends Action = Action> = (
201
reducer: ActionReducer<T, V>
202
) => ActionReducer<T, V>;
203
```
204
205
## Advanced Usage Patterns
206
207
### Nested State Updates
208
209
```typescript
210
import { createReducer, on } from "@ngrx/store";
211
import { updateUserProfile, updateUserPreferences } from "./user.actions";
212
213
interface UserState {
214
profile: {
215
name: string;
216
email: string;
217
avatar: string;
218
};
219
preferences: {
220
theme: 'light' | 'dark';
221
notifications: boolean;
222
language: string;
223
};
224
metadata: {
225
lastLogin: number;
226
loginCount: number;
227
};
228
}
229
230
export const userReducer = createReducer(
231
initialUserState,
232
233
// Nested state update
234
on(updateUserProfile, (state, { profile }) => ({
235
...state,
236
profile: {
237
...state.profile,
238
...profile
239
}
240
})),
241
242
// Deep nested update
243
on(updateUserPreferences, (state, { preferences }) => ({
244
...state,
245
preferences: {
246
...state.preferences,
247
...preferences
248
},
249
metadata: {
250
...state.metadata,
251
lastUpdated: Date.now()
252
}
253
}))
254
);
255
```
256
257
### Conditional State Updates
258
259
```typescript
260
export const gameReducer = createReducer(
261
initialGameState,
262
263
on(makeMove, (state, { move }) => {
264
// Conditional logic in reducer
265
if (state.gameOver) {
266
return state; // No changes if game is over
267
}
268
269
const newBoard = applyMove(state.board, move);
270
const isGameOver = checkGameOver(newBoard);
271
272
return {
273
...state,
274
board: newBoard,
275
currentPlayer: state.currentPlayer === 'X' ? 'O' : 'X',
276
gameOver: isGameOver,
277
winner: isGameOver ? getWinner(newBoard) : null,
278
moves: [...state.moves, move]
279
};
280
}),
281
282
on(resetGame, () => initialGameState)
283
);
284
```
285
286
### Array State Management
287
288
```typescript
289
export const todosReducer = createReducer(
290
initialTodosState,
291
292
// Add item to array
293
on(addTodo, (state, { todo }) => ({
294
...state,
295
todos: [...state.todos, { ...todo, id: generateId() }]
296
})),
297
298
// Update item in array
299
on(updateTodo, (state, { id, changes }) => ({
300
...state,
301
todos: state.todos.map(todo =>
302
todo.id === id ? { ...todo, ...changes } : todo
303
)
304
})),
305
306
// Remove item from array
307
on(deleteTodo, (state, { id }) => ({
308
...state,
309
todos: state.todos.filter(todo => todo.id !== id)
310
})),
311
312
// Bulk operations
313
on(toggleAllTodos, (state) => {
314
const allCompleted = state.todos.every(todo => todo.completed);
315
return {
316
...state,
317
todos: state.todos.map(todo => ({
318
...todo,
319
completed: !allCompleted
320
}))
321
};
322
})
323
);
324
```
325
326
## Utility Functions
327
328
### Combine Reducers
329
330
Combines multiple reducers into a single reducer function for managing different parts of the state tree.
331
332
```typescript { .api }
333
/**
334
* Combines reducers for individual features into a single reducer
335
* @param reducers - Object mapping keys of the root state to their corresponding feature reducer
336
* @param initialState - Provides a state value if the current state is undefined
337
* @returns A reducer function that delegates to feature reducers
338
*/
339
function combineReducers<T, V extends Action = Action>(
340
reducers: ActionReducerMap<T, V>,
341
initialState?: Partial<T>
342
): ActionReducer<T, V>;
343
```
344
345
**Usage Examples:**
346
347
```typescript
348
import { combineReducers } from "@ngrx/store";
349
import { counterReducer } from "./counter.reducer";
350
import { userReducer } from "./user.reducer";
351
import { todosReducer } from "./todos.reducer";
352
353
// Combine feature reducers into root reducer
354
export const rootReducer = combineReducers({
355
counter: counterReducer,
356
user: userReducer,
357
todos: todosReducer
358
});
359
360
// With initial state override
361
export const rootReducerWithDefaults = combineReducers({
362
counter: counterReducer,
363
user: userReducer,
364
todos: todosReducer
365
}, {
366
counter: { count: 10 },
367
user: { isLoggedIn: false },
368
todos: { items: [], filter: 'all' }
369
});
370
```
371
372
### Compose
373
374
Function composition utility for combining multiple functions, commonly used with meta-reducers.
375
376
```typescript { .api }
377
/**
378
* Composes multiple functions into a single function
379
* @param functions - Functions to compose (applied right to left)
380
* @returns Composed function that applies all input functions in sequence
381
*/
382
function compose<A>(): (i: A) => A;
383
function compose<A, B>(b: (i: A) => B): (i: A) => B;
384
function compose<A, B, C>(c: (i: B) => C, b: (i: A) => B): (i: A) => C;
385
function compose<A, B, C, D>(
386
d: (i: C) => D,
387
c: (i: B) => C,
388
b: (i: A) => B
389
): (i: A) => D;
390
function compose<A, B, C, D, E>(
391
e: (i: D) => E,
392
d: (i: C) => D,
393
c: (i: B) => C,
394
b: (i: A) => B
395
): (i: A) => E;
396
function compose<A, B, C, D, E, F>(
397
f: (i: E) => F,
398
e: (i: D) => E,
399
d: (i: C) => D,
400
c: (i: B) => C,
401
b: (i: A) => B
402
): (i: A) => F;
403
function compose<A = any, F = any>(...functions: any[]): (i: A) => F;
404
```
405
406
**Usage Examples:**
407
408
```typescript
409
import { compose, MetaReducer } from "@ngrx/store";
410
411
// Create meta-reducers
412
const loggingMetaReducer: MetaReducer<any> = (reducer) => (state, action) => {
413
console.log('Action:', action.type);
414
const nextState = reducer(state, action);
415
console.log('Next State:', nextState);
416
return nextState;
417
};
418
419
const freezeMetaReducer: MetaReducer<any> = (reducer) => (state, action) => {
420
return Object.freeze(reducer(state, action));
421
};
422
423
// Compose meta-reducers
424
const composedMetaReducer = compose(
425
freezeMetaReducer,
426
loggingMetaReducer
427
);
428
429
// Use with reducer factory
430
const enhancedReducer = composedMetaReducer(baseReducer);
431
```
432
433
### Create Reducer Factory
434
435
Creates a reducer factory that can apply meta-reducers to a collection of reducers.
436
437
```typescript { .api }
438
/**
439
* Creates a reducer factory with optional meta-reducers
440
* @param reducerFactory - Factory function for creating reducers
441
* @param metaReducers - Array of meta-reducers to apply
442
* @returns Enhanced reducer factory with meta-reducers applied
443
*/
444
function createReducerFactory<T, V extends Action = Action>(
445
reducerFactory: ActionReducerFactory<T, V>,
446
metaReducers?: MetaReducer<T, V>[]
447
): ActionReducerFactory<T, V>;
448
```
449
450
**Usage Examples:**
451
452
```typescript
453
import { createReducerFactory, combineReducers, MetaReducer } from "@ngrx/store";
454
455
// Create meta-reducers
456
const loggerMetaReducer: MetaReducer<any> = (reducer) => (state, action) => {
457
console.log(`[${action.type}]`, state);
458
return reducer(state, action);
459
};
460
461
const immutabilityMetaReducer: MetaReducer<any> = (reducer) => (state, action) => {
462
const result = reducer(state, action);
463
return Object.freeze(result);
464
};
465
466
// Create enhanced reducer factory
467
const enhancedReducerFactory = createReducerFactory(
468
combineReducers,
469
[loggerMetaReducer, immutabilityMetaReducer]
470
);
471
472
// Use factory to create root reducer with meta-reducers applied
473
const rootReducer = enhancedReducerFactory({
474
counter: counterReducer,
475
user: userReducer,
476
todos: todosReducer
477
});
478
```
479
480
### Create Feature Reducer Factory
481
482
Creates a factory for feature reducers with meta-reducer support.
483
484
```typescript { .api }
485
/**
486
* Creates a feature reducer factory with optional meta-reducers
487
* @param metaReducers - Array of meta-reducers to apply to feature reducers
488
* @returns Factory function for creating feature reducers with meta-reducers
489
*/
490
function createFeatureReducerFactory<T, V extends Action = Action>(
491
metaReducers?: MetaReducer<T, V>[]
492
): (reducer: ActionReducer<T, V>, initialState?: T) => ActionReducer<T, V>;
493
```
494
495
**Usage Examples:**
496
497
```typescript
498
import { createFeatureReducerFactory, MetaReducer } from "@ngrx/store";
499
500
// Create feature-specific meta-reducers
501
const featureLoggerMetaReducer: MetaReducer<any> = (reducer) => (state, action) => {
502
if (action.type.startsWith('[Feature]')) {
503
console.log('Feature action:', action.type, state);
504
}
505
return reducer(state, action);
506
};
507
508
// Create feature reducer factory
509
const featureReducerFactory = createFeatureReducerFactory([
510
featureLoggerMetaReducer
511
]);
512
513
// Use factory for feature reducers
514
const enhancedFeatureReducer = featureReducerFactory(
515
baseFeatureReducer,
516
initialFeatureState
517
);
518
```
519
520
## Best Practices
521
522
1. **Immutable Updates**: Always return new state objects, never mutate existing state
523
2. **Type Safety**: Use TypeScript interfaces for state and action payloads
524
3. **Pure Functions**: Reducers should be pure functions without side effects
525
4. **Minimal Logic**: Keep complex logic in services/effects, not reducers
526
5. **Consistent Structure**: Use consistent state shape and update patterns
527
6. **Error Handling**: Handle error actions to maintain application stability
528
7. **Meta-Reducers**: Use meta-reducers for cross-cutting concerns like logging, immutability checks
529
8. **Composition**: Leverage `compose` for clean meta-reducer composition