or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

action-generation.mdcomponent-store.mdcontainer-components.mddata-services.mdeffect-generation.mdentity-management.mdfeature-generation.mdindex.mdngrx-push-migration.mdreducer-generation.mdselector-generation.mdstore-setup.mdutility-functions.md

entity-management.mddocs/

0

# Entity Management

1

2

NgRx entity generation schematic that creates entity-based state management using @ngrx/entity for collections of data with CRUD operations. This schematic provides optimized state management for normalized data with built-in adapter functions.

3

4

## Capabilities

5

6

### Entity Schematic

7

8

Generates NgRx entity state management with EntityAdapter for efficient collection handling.

9

10

```bash

11

# Basic entity generation

12

ng generate @ngrx/schematics:entity User

13

14

# Entity with creator functions

15

ng generate @ngrx/schematics:entity User --creators

16

17

# Entity as feature state

18

ng generate @ngrx/schematics:entity Product --feature

19

```

20

21

```typescript { .api }

22

/**

23

* Entity schematic configuration interface

24

*/

25

interface EntitySchema {

26

/** Name of the entity (typically entity or model name) */

27

name: string;

28

/** Path where entity files should be generated */

29

path?: string;

30

/** Angular project to target */

31

project?: string;

32

/** Generate files without creating a folder */

33

flat?: boolean;

34

/** Group entity files within folders */

35

group?: boolean;

36

/** Module file to register entity in */

37

module?: string;

38

/** Path to existing reducers file */

39

reducers?: string;

40

/** Generate as feature entity */

41

feature?: boolean;

42

/** Use creator functions */

43

creators?: boolean;

44

}

45

```

46

47

### Entity State Structure

48

49

Creates normalized entity state using @ngrx/entity EntityAdapter:

50

51

```typescript

52

// Generated entity state interface

53

import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';

54

55

export interface User {

56

id: string;

57

name: string;

58

email: string;

59

active: boolean;

60

}

61

62

export interface UserState extends EntityState<User> {

63

// Additional state properties

64

selectedUserId: string | null;

65

loading: boolean;

66

error: string | null;

67

filter: UserFilter | null;

68

}

69

70

export const userAdapter: EntityAdapter<User> = createEntityAdapter<User>();

71

72

export const initialState: UserState = userAdapter.getInitialState({

73

selectedUserId: null,

74

loading: false,

75

error: null,

76

filter: null

77

});

78

```

79

80

**Usage Examples:**

81

82

```bash

83

# Generate user entity

84

ng generate @ngrx/schematics:entity User --creators --feature

85

86

# Generate product entity with custom path

87

ng generate @ngrx/schematics:entity Product --path=src/app/catalog --creators

88

89

# Generate entity and register in module

90

ng generate @ngrx/schematics:entity Order --module=order/order.module.ts

91

```

92

93

### Entity Actions

94

95

Generates comprehensive CRUD actions for entity management:

96

97

```typescript

98

// Generated entity actions

99

import { createAction, props } from '@ngrx/store';

100

import { Update } from '@ngrx/entity';

101

102

// Load actions

103

export const loadUsers = createAction('[User/API] Load Users');

104

105

export const loadUsersSuccess = createAction(

106

'[User/API] Load Users Success',

107

props<{ users: User[] }>()

108

);

109

110

export const loadUsersFailure = createAction(

111

'[User/API] Load Users Failure',

112

props<{ error: any }>()

113

);

114

115

// Load single entity

116

export const loadUser = createAction(

117

'[User/API] Load User',

118

props<{ id: string }>()

119

);

120

121

export const loadUserSuccess = createAction(

122

'[User/API] Load User Success',

123

props<{ user: User }>()

124

);

125

126

// Add entity

127

export const addUser = createAction(

128

'[User/API] Add User',

129

props<{ user: User }>()

130

);

131

132

export const addUserSuccess = createAction(

133

'[User/API] Add User Success',

134

props<{ user: User }>()

135

);

136

137

export const addUserFailure = createAction(

138

'[User/API] Add User Failure',

139

props<{ error: any }>()

140

);

141

142

// Update entity

143

export const updateUser = createAction(

144

'[User/API] Update User',

145

props<{ user: Update<User> }>()

146

);

147

148

export const updateUserSuccess = createAction(

149

'[User/API] Update User Success',

150

props<{ user: Update<User> }>()

151

);

152

153

export const updateUserFailure = createAction(

154

'[User/API] Update User Failure',

155

props<{ error: any }>()

156

);

157

158

// Delete entity

159

export const deleteUser = createAction(

160

'[User/API] Delete User',

161

props<{ id: string }>()

162

);

163

164

export const deleteUserSuccess = createAction(

165

'[User/API] Delete User Success',

166

props<{ id: string }>()

167

);

168

169

export const deleteUserFailure = createAction(

170

'[User/API] Delete User Failure',

171

props<{ error: any }>()

172

);

173

174

// Selection actions

175

export const selectUser = createAction(

176

'[User] Select User',

177

props<{ userId: string }>()

178

);

179

180

export const clearSelection = createAction('[User] Clear Selection');

181

```

182

183

### Entity Reducer

184

185

Creates reducer using EntityAdapter methods for efficient entity operations:

186

187

```typescript

188

// Generated entity reducer

189

import { createReducer, on } from '@ngrx/store';

190

import * as UserActions from './user.actions';

191

192

export const userFeatureKey = 'user';

193

194

export const userReducer = createReducer(

195

initialState,

196

197

// Load all entities

198

on(UserActions.loadUsers, (state) => ({

199

...state,

200

loading: true,

201

error: null

202

})),

203

204

on(UserActions.loadUsersSuccess, (state, { users }) =>

205

userAdapter.setAll(users, {

206

...state,

207

loading: false,

208

error: null

209

})

210

),

211

212

on(UserActions.loadUsersFailure, (state, { error }) => ({

213

...state,

214

loading: false,

215

error

216

})),

217

218

// Load single entity

219

on(UserActions.loadUserSuccess, (state, { user }) =>

220

userAdapter.upsertOne(user, state)

221

),

222

223

// Add entity

224

on(UserActions.addUserSuccess, (state, { user }) =>

225

userAdapter.addOne(user, state)

226

),

227

228

// Update entity

229

on(UserActions.updateUserSuccess, (state, { user }) =>

230

userAdapter.updateOne(user, state)

231

),

232

233

// Delete entity

234

on(UserActions.deleteUserSuccess, (state, { id }) =>

235

userAdapter.removeOne(id, state)

236

),

237

238

// Selection

239

on(UserActions.selectUser, (state, { userId }) => ({

240

...state,

241

selectedUserId: userId

242

})),

243

244

on(UserActions.clearSelection, (state) => ({

245

...state,

246

selectedUserId: null

247

}))

248

);

249

```

250

251

### Entity Selectors

252

253

Generates comprehensive selectors using EntityAdapter selector methods:

254

255

```typescript

256

// Generated entity selectors

257

import { createFeatureSelector, createSelector } from '@ngrx/store';

258

259

export const selectUserState = createFeatureSelector<UserState>('user');

260

261

// Entity adapter selectors

262

export const {

263

selectIds: selectUserIds,

264

selectEntities: selectUserEntities,

265

selectAll: selectAllUsers,

266

selectTotal: selectUserTotal

267

} = userAdapter.getSelectors(selectUserState);

268

269

// Additional selectors

270

export const selectUsersLoading = createSelector(

271

selectUserState,

272

(state: UserState) => state.loading

273

);

274

275

export const selectUsersError = createSelector(

276

selectUserState,

277

(state: UserState) => state.error

278

);

279

280

export const selectSelectedUserId = createSelector(

281

selectUserState,

282

(state: UserState) => state.selectedUserId

283

);

284

285

export const selectSelectedUser = createSelector(

286

selectUserEntities,

287

selectSelectedUserId,

288

(entities, selectedId) => selectedId ? entities[selectedId] : null

289

);

290

291

// Filtered selectors

292

export const selectActiveUsers = createSelector(

293

selectAllUsers,

294

(users) => users.filter(user => user.active)

295

);

296

297

export const selectUsersByRole = (role: string) => createSelector(

298

selectAllUsers,

299

(users) => users.filter(user => user.role === role)

300

);

301

```

302

303

### Entity Adapter Methods

304

305

The generated entity state includes all EntityAdapter methods for entity manipulation:

306

307

```typescript { .api }

308

/**

309

* EntityAdapter methods available in generated reducer

310

*/

311

interface EntityAdapterMethods<T> {

312

/** Add one entity */

313

addOne: (entity: T, state: EntityState<T>) => EntityState<T>;

314

/** Add multiple entities */

315

addMany: (entities: T[], state: EntityState<T>) => EntityState<T>;

316

/** Add all entities (replace existing) */

317

setAll: (entities: T[], state: EntityState<T>) => EntityState<T>;

318

/** Remove one entity */

319

removeOne: (id: string, state: EntityState<T>) => EntityState<T>;

320

/** Remove multiple entities */

321

removeMany: (ids: string[], state: EntityState<T>) => EntityState<T>;

322

/** Remove all entities */

323

removeAll: (state: EntityState<T>) => EntityState<T>;

324

/** Update one entity */

325

updateOne: (update: Update<T>, state: EntityState<T>) => EntityState<T>;

326

/** Update multiple entities */

327

updateMany: (updates: Update<T>[], state: EntityState<T>) => EntityState<T>;

328

/** Add or update one entity */

329

upsertOne: (entity: T, state: EntityState<T>) => EntityState<T>;

330

/** Add or update multiple entities */

331

upsertMany: (entities: T[], state: EntityState<T>) => EntityState<T>;

332

}

333

334

/**

335

* Update interface for partial entity updates

336

*/

337

interface Update<T> {

338

id: string;

339

changes: Partial<T>;

340

}

341

```

342

343

### Custom Sort and ID Selection

344

345

Entity adapter supports custom sorting and ID selection:

346

347

```typescript

348

// Custom entity adapter configuration

349

export const userAdapter: EntityAdapter<User> = createEntityAdapter<User>({

350

// Custom ID selector

351

selectId: (user: User) => user.id,

352

353

// Custom sort comparator

354

sortComparer: (a: User, b: User) => a.name.localeCompare(b.name)

355

});

356

357

// With custom ID field

358

export interface Product {

359

productId: string;

360

name: string;

361

price: number;

362

}

363

364

export const productAdapter: EntityAdapter<Product> = createEntityAdapter<Product>({

365

selectId: (product: Product) => product.productId,

366

sortComparer: (a: Product, b: Product) => a.name.localeCompare(b.name)

367

});

368

```

369

370

### Entity Effects

371

372

Generates effects for entity CRUD operations:

373

374

```typescript

375

// Generated entity effects

376

import { Injectable } from '@angular/core';

377

import { Actions, createEffect, ofType } from '@ngrx/effects';

378

import { catchError, map, switchMap, mergeMap } from 'rxjs/operators';

379

import { of } from 'rxjs';

380

import * as UserActions from './user.actions';

381

import { UserService } from './user.service';

382

383

@Injectable()

384

export class UserEffects {

385

386

loadUsers$ = createEffect(() =>

387

this.actions$.pipe(

388

ofType(UserActions.loadUsers),

389

switchMap(() =>

390

this.userService.getUsers().pipe(

391

map(users => UserActions.loadUsersSuccess({ users })),

392

catchError(error => of(UserActions.loadUsersFailure({ error })))

393

)

394

)

395

)

396

);

397

398

loadUser$ = createEffect(() =>

399

this.actions$.pipe(

400

ofType(UserActions.loadUser),

401

mergeMap(({ id }) =>

402

this.userService.getUserById(id).pipe(

403

map(user => UserActions.loadUserSuccess({ user })),

404

catchError(error => of(UserActions.loadUsersFailure({ error })))

405

)

406

)

407

)

408

);

409

410

addUser$ = createEffect(() =>

411

this.actions$.pipe(

412

ofType(UserActions.addUser),

413

switchMap(({ user }) =>

414

this.userService.createUser(user).pipe(

415

map(createdUser => UserActions.addUserSuccess({ user: createdUser })),

416

catchError(error => of(UserActions.addUserFailure({ error })))

417

)

418

)

419

)

420

);

421

422

updateUser$ = createEffect(() =>

423

this.actions$.pipe(

424

ofType(UserActions.updateUser),

425

switchMap(({ user }) =>

426

this.userService.updateUser(user).pipe(

427

map(updatedUser => UserActions.updateUserSuccess({ user: updatedUser })),

428

catchError(error => of(UserActions.updateUserFailure({ error })))

429

)

430

)

431

)

432

);

433

434

deleteUser$ = createEffect(() =>

435

this.actions$.pipe(

436

ofType(UserActions.deleteUser),

437

switchMap(({ id }) =>

438

this.userService.deleteUser(id).pipe(

439

map(() => UserActions.deleteUserSuccess({ id })),

440

catchError(error => of(UserActions.deleteUserFailure({ error })))

441

)

442

)

443

)

444

);

445

446

constructor(

447

private actions$: Actions,

448

private userService: UserService

449

) {}

450

}

451

```

452

453

### Entity Testing

454

455

Generated entity state includes comprehensive testing utilities:

456

457

```typescript

458

// Entity testing examples

459

describe('User Entity', () => {

460

const users: User[] = [

461

{ id: '1', name: 'John', email: 'john@example.com', active: true },

462

{ id: '2', name: 'Jane', email: 'jane@example.com', active: false }

463

];

464

465

describe('UserReducer', () => {

466

it('should load users', () => {

467

const action = UserActions.loadUsersSuccess({ users });

468

const result = userReducer(initialState, action);

469

470

expect(result.ids).toEqual(['1', '2']);

471

expect(result.entities['1']).toEqual(users[0]);

472

expect(result.loading).toBe(false);

473

});

474

475

it('should add user', () => {

476

const newUser: User = { id: '3', name: 'Bob', email: 'bob@example.com', active: true };

477

const action = UserActions.addUserSuccess({ user: newUser });

478

const result = userReducer(initialState, action);

479

480

expect(result.ids).toContain('3');

481

expect(result.entities['3']).toEqual(newUser);

482

});

483

484

it('should update user', () => {

485

const stateWithUsers = userAdapter.setAll(users, initialState);

486

const update: Update<User> = { id: '1', changes: { name: 'John Updated' } };

487

const action = UserActions.updateUserSuccess({ user: update });

488

const result = userReducer(stateWithUsers, action);

489

490

expect(result.entities['1']?.name).toBe('John Updated');

491

});

492

493

it('should delete user', () => {

494

const stateWithUsers = userAdapter.setAll(users, initialState);

495

const action = UserActions.deleteUserSuccess({ id: '1' });

496

const result = userReducer(stateWithUsers, action);

497

498

expect(result.ids).not.toContain('1');

499

expect(result.entities['1']).toBeUndefined();

500

});

501

});

502

503

describe('User Selectors', () => {

504

const stateWithUsers = userAdapter.setAll(users, {

505

...initialState,

506

selectedUserId: '1'

507

});

508

509

it('should select all users', () => {

510

const result = selectAllUsers.projector(stateWithUsers);

511

expect(result).toEqual(users);

512

});

513

514

it('should select user entities', () => {

515

const result = selectUserEntities.projector(stateWithUsers);

516

expect(result['1']).toEqual(users[0]);

517

});

518

519

it('should select selected user', () => {

520

const result = selectSelectedUser.projector(stateWithUsers.entities, '1');

521

expect(result).toEqual(users[0]);

522

});

523

});

524

});

525

```

526

527

### Performance Benefits

528

529

Entity state management provides significant performance benefits:

530

531

```typescript { .api }

532

/**

533

* Performance benefits of entity state

534

*/

535

interface EntityPerformanceBenefits {

536

/** O(1) entity lookups by ID */

537

fastLookup: 'entities[id] access';

538

/** Efficient updates without array iteration */

539

efficientUpdates: 'Direct entity replacement';

540

/** Normalized data structure */

541

normalizedData: 'Eliminates data duplication';

542

/** Memoized selectors */

543

memoizedSelectors: 'Automatic selector memoization';

544

/** Optimized change detection */

545

changeDetection: 'Minimal component re-renders';

546

}

547

```