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
```