or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

action-creators.mdfeature-management.mdindex.mdmodule-configuration.mdreducer-creators.mdselectors.mdstandalone-providers.mdstore-service.mdtesting-utilities.md

reducer-creators.mddocs/

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