0
# Feature Generation
1
2
NgRx feature generation schematic that creates complete feature modules with actions, reducers, effects, and selectors in a single command. This schematic provides a convenient way to scaffold an entire state management feature with all necessary NgRx components.
3
4
## Capabilities
5
6
### Feature Schematic
7
8
Generates a complete NgRx feature with all related state management files and proper module registration.
9
10
```bash
11
# Basic feature generation
12
ng generate @ngrx/schematics:feature User
13
14
# Feature with API actions
15
ng generate @ngrx/schematics:feature User --api
16
17
# Feature with creator functions
18
ng generate @ngrx/schematics:feature User --creators
19
```
20
21
```typescript { .api }
22
/**
23
* Feature schematic configuration interface
24
*/
25
interface FeatureSchema {
26
/** Name of the feature (typically entity or domain name) */
27
name: string;
28
/** Path where feature 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 feature files within folders */
35
group?: boolean;
36
/** Module file to register feature in */
37
module?: string;
38
/** Path to existing reducers file */
39
reducers?: string;
40
/** Generate as feature state */
41
feature?: boolean;
42
/** Use creator functions */
43
creators?: boolean;
44
/** Generate API success and failure actions */
45
api?: boolean;
46
}
47
```
48
49
### Complete Feature Structure
50
51
The feature schematic generates a comprehensive set of files for state management:
52
53
```
54
src/app/user/
55
├── user.actions.ts # Action definitions
56
├── user.reducer.ts # Reducer with state interface
57
├── user.effects.ts # Effects for side effects
58
├── user.selectors.ts # Selectors for state access
59
└── index.ts # Barrel exports
60
```
61
62
**Usage Examples:**
63
64
```bash
65
# Generate complete user feature
66
ng generate @ngrx/schematics:feature User --creators --api
67
68
# Generate product feature with custom path
69
ng generate @ngrx/schematics:feature Product --path=src/app/catalog --creators
70
71
# Generate feature and register in specific module
72
ng generate @ngrx/schematics:feature Order --module=order/order.module.ts
73
```
74
75
### Generated Actions
76
77
Creates comprehensive action definitions with optional API patterns:
78
79
```typescript
80
// Generated feature actions
81
import { createAction, props } from '@ngrx/store';
82
83
// Load actions
84
export const loadUsers = createAction('[User/API] Load Users');
85
86
export const loadUsersSuccess = createAction(
87
'[User/API] Load Users Success',
88
props<{ users: User[] }>()
89
);
90
91
export const loadUsersFailure = createAction(
92
'[User/API] Load Users Failure',
93
props<{ error: any }>()
94
);
95
96
// CRUD actions (when --api flag is used)
97
export const createUser = createAction(
98
'[User/API] Create User',
99
props<{ user: User }>()
100
);
101
102
export const createUserSuccess = createAction(
103
'[User/API] Create User Success',
104
props<{ user: User }>()
105
);
106
107
export const createUserFailure = createAction(
108
'[User/API] Create User Failure',
109
props<{ error: any }>()
110
);
111
112
// Selection actions
113
export const selectUser = createAction(
114
'[User] Select User',
115
props<{ userId: string }>()
116
);
117
118
export const clearSelection = createAction('[User] Clear Selection');
119
```
120
121
### Generated Reducer
122
123
Creates a complete reducer with state interface and action handlers:
124
125
```typescript
126
// Generated feature reducer
127
import { createReducer, on } from '@ngrx/store';
128
import * as UserActions from './user.actions';
129
130
export const userFeatureKey = 'user';
131
132
export interface UserState {
133
users: User[];
134
loading: boolean;
135
error: string | null;
136
selectedUserId: string | null;
137
}
138
139
export const initialState: UserState = {
140
users: [],
141
loading: false,
142
error: null,
143
selectedUserId: null
144
};
145
146
export const userReducer = createReducer(
147
initialState,
148
149
on(UserActions.loadUsers, (state) => ({
150
...state,
151
loading: true,
152
error: null
153
})),
154
155
on(UserActions.loadUsersSuccess, (state, { users }) => ({
156
...state,
157
users,
158
loading: false
159
})),
160
161
on(UserActions.loadUsersFailure, (state, { error }) => ({
162
...state,
163
loading: false,
164
error
165
})),
166
167
on(UserActions.selectUser, (state, { userId }) => ({
168
...state,
169
selectedUserId: userId
170
})),
171
172
on(UserActions.clearSelection, (state) => ({
173
...state,
174
selectedUserId: null
175
}))
176
);
177
```
178
179
### Generated Effects
180
181
Creates effects for handling side effects and API calls:
182
183
```typescript
184
// Generated feature effects
185
import { Injectable } from '@angular/core';
186
import { Actions, createEffect, ofType } from '@ngrx/effects';
187
import { catchError, map, switchMap } from 'rxjs/operators';
188
import { of } from 'rxjs';
189
import * as UserActions from './user.actions';
190
import { UserService } from './user.service';
191
192
@Injectable()
193
export class UserEffects {
194
195
loadUsers$ = createEffect(() =>
196
this.actions$.pipe(
197
ofType(UserActions.loadUsers),
198
switchMap(() =>
199
this.userService.getUsers().pipe(
200
map(users => UserActions.loadUsersSuccess({ users })),
201
catchError(error => of(UserActions.loadUsersFailure({ error })))
202
)
203
)
204
)
205
);
206
207
createUser$ = createEffect(() =>
208
this.actions$.pipe(
209
ofType(UserActions.createUser),
210
switchMap(({ user }) =>
211
this.userService.createUser(user).pipe(
212
map(createdUser => UserActions.createUserSuccess({ user: createdUser })),
213
catchError(error => of(UserActions.createUserFailure({ error })))
214
)
215
)
216
)
217
);
218
219
constructor(
220
private actions$: Actions,
221
private userService: UserService
222
) {}
223
}
224
```
225
226
### Generated Selectors
227
228
Creates comprehensive selectors for accessing feature state:
229
230
```typescript
231
// Generated feature selectors
232
import { createFeatureSelector, createSelector } from '@ngrx/store';
233
import { UserState } from './user.reducer';
234
235
export const selectUserState = createFeatureSelector<UserState>('user');
236
237
export const selectUsers = createSelector(
238
selectUserState,
239
(state: UserState) => state.users
240
);
241
242
export const selectUsersLoading = createSelector(
243
selectUserState,
244
(state: UserState) => state.loading
245
);
246
247
export const selectUsersError = createSelector(
248
selectUserState,
249
(state: UserState) => state.error
250
);
251
252
export const selectSelectedUserId = createSelector(
253
selectUserState,
254
(state: UserState) => state.selectedUserId
255
);
256
257
export const selectSelectedUser = createSelector(
258
selectUsers,
259
selectSelectedUserId,
260
(users, selectedId) => users.find(user => user.id === selectedId)
261
);
262
```
263
264
### Module Integration
265
266
The feature schematic automatically registers the feature in the specified module:
267
268
```typescript
269
// Module registration
270
import { StoreModule } from '@ngrx/store';
271
import { EffectsModule } from '@ngrx/effects';
272
import * as fromUser from './user.reducer';
273
import { UserEffects } from './user.effects';
274
275
@NgModule({
276
imports: [
277
StoreModule.forFeature(fromUser.userFeatureKey, fromUser.userReducer),
278
EffectsModule.forFeature([UserEffects])
279
]
280
})
281
export class UserModule {}
282
```
283
284
### Barrel Exports
285
286
Creates comprehensive barrel exports for easy importing:
287
288
```typescript
289
// Generated index.ts
290
export * from './user.actions';
291
export * from './user.reducer';
292
export * from './user.effects';
293
export * from './user.selectors';
294
```
295
296
### Creator Functions Support
297
298
When using `--creators` flag, all generated code uses NgRx creator functions:
299
300
```typescript { .api }
301
/**
302
* Creator function patterns used in generated code
303
*/
304
interface CreatorPatterns {
305
/** Actions with createAction */
306
actions: 'createAction(type, props<PayloadType>())';
307
/** Reducers with createReducer */
308
reducers: 'createReducer(initialState, on(action, reducer))';
309
/** Effects with createEffect */
310
effects: 'createEffect(() => source$)';
311
/** Selectors with createSelector */
312
selectors: 'createSelector(input, projector)';
313
}
314
```
315
316
### Feature Configuration
317
318
The generated feature includes proper TypeScript configuration and type safety:
319
320
```typescript
321
// Type-safe feature configuration
322
export interface AppState {
323
[fromUser.userFeatureKey]: fromUser.UserState;
324
}
325
326
// Feature key constant
327
export const userFeatureKey = 'user';
328
329
// State interface
330
export interface UserState {
331
users: User[];
332
loading: boolean;
333
error: string | null;
334
selectedUserId: string | null;
335
}
336
```
337
338
### Testing Setup
339
340
Generated features include testing utilities and examples:
341
342
```typescript
343
// Feature testing setup
344
describe('User Feature', () => {
345
let store: MockStore;
346
let effects: UserEffects;
347
let service: jasmine.SpyObj<UserService>;
348
349
beforeEach(() => {
350
TestBed.configureTestingModule({
351
providers: [
352
provideMockStore({ initialState }),
353
UserEffects,
354
{
355
provide: UserService,
356
useValue: jasmine.createSpyObj('UserService', ['getUsers'])
357
}
358
]
359
});
360
361
store = TestBed.inject(MockStore);
362
effects = TestBed.inject(UserEffects);
363
service = TestBed.inject(UserService) as jasmine.SpyObj<UserService>;
364
});
365
366
it('should load users', () => {
367
const users = [{ id: '1', name: 'John' }];
368
store.dispatch(UserActions.loadUsers());
369
370
store.select(selectUsers).subscribe(result => {
371
expect(result).toEqual(users);
372
});
373
});
374
});
375
```
376
377
### Performance Considerations
378
379
Generated features include performance optimizations:
380
381
```typescript
382
// Optimized state updates
383
on(UserActions.updateUserSuccess, (state, { user }) => {
384
const index = state.users.findIndex(u => u.id === user.id);
385
if (index === -1) return state;
386
387
const users = [...state.users];
388
users[index] = user;
389
390
return { ...state, users };
391
});
392
393
// Memoized selectors
394
export const selectUserById = (id: string) => createSelector(
395
selectUsers,
396
(users) => users.find(user => user.id === id)
397
);
398
```