Angular CLI schematics for generating NgRx state management code including actions, reducers, effects, selectors, and feature modules.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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.
Generates NgRx entity state management with EntityAdapter for efficient collection handling.
# Basic entity generation
ng generate @ngrx/schematics:entity User
# Entity with creator functions
ng generate @ngrx/schematics:entity User --creators
# Entity as feature state
ng generate @ngrx/schematics:entity Product --feature/**
* Entity schematic configuration interface
*/
interface EntitySchema {
/** Name of the entity (typically entity or model name) */
name: string;
/** Path where entity files should be generated */
path?: string;
/** Angular project to target */
project?: string;
/** Generate files without creating a folder */
flat?: boolean;
/** Group entity files within folders */
group?: boolean;
/** Module file to register entity in */
module?: string;
/** Path to existing reducers file */
reducers?: string;
/** Generate as feature entity */
feature?: boolean;
/** Use creator functions */
creators?: boolean;
}Creates normalized entity state using @ngrx/entity EntityAdapter:
// Generated entity state interface
import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
export interface User {
id: string;
name: string;
email: string;
active: boolean;
}
export interface UserState extends EntityState<User> {
// Additional state properties
selectedUserId: string | null;
loading: boolean;
error: string | null;
filter: UserFilter | null;
}
export const userAdapter: EntityAdapter<User> = createEntityAdapter<User>();
export const initialState: UserState = userAdapter.getInitialState({
selectedUserId: null,
loading: false,
error: null,
filter: null
});Usage Examples:
# Generate user entity
ng generate @ngrx/schematics:entity User --creators --feature
# Generate product entity with custom path
ng generate @ngrx/schematics:entity Product --path=src/app/catalog --creators
# Generate entity and register in module
ng generate @ngrx/schematics:entity Order --module=order/order.module.tsGenerates comprehensive CRUD actions for entity management:
// Generated entity actions
import { createAction, props } from '@ngrx/store';
import { Update } from '@ngrx/entity';
// Load actions
export const loadUsers = createAction('[User/API] Load Users');
export const loadUsersSuccess = createAction(
'[User/API] Load Users Success',
props<{ users: User[] }>()
);
export const loadUsersFailure = createAction(
'[User/API] Load Users Failure',
props<{ error: any }>()
);
// Load single entity
export const loadUser = createAction(
'[User/API] Load User',
props<{ id: string }>()
);
export const loadUserSuccess = createAction(
'[User/API] Load User Success',
props<{ user: User }>()
);
// Add entity
export const addUser = createAction(
'[User/API] Add User',
props<{ user: User }>()
);
export const addUserSuccess = createAction(
'[User/API] Add User Success',
props<{ user: User }>()
);
export const addUserFailure = createAction(
'[User/API] Add User Failure',
props<{ error: any }>()
);
// Update entity
export const updateUser = createAction(
'[User/API] Update User',
props<{ user: Update<User> }>()
);
export const updateUserSuccess = createAction(
'[User/API] Update User Success',
props<{ user: Update<User> }>()
);
export const updateUserFailure = createAction(
'[User/API] Update User Failure',
props<{ error: any }>()
);
// Delete entity
export const deleteUser = createAction(
'[User/API] Delete User',
props<{ id: string }>()
);
export const deleteUserSuccess = createAction(
'[User/API] Delete User Success',
props<{ id: string }>()
);
export const deleteUserFailure = createAction(
'[User/API] Delete User Failure',
props<{ error: any }>()
);
// Selection actions
export const selectUser = createAction(
'[User] Select User',
props<{ userId: string }>()
);
export const clearSelection = createAction('[User] Clear Selection');Creates reducer using EntityAdapter methods for efficient entity operations:
// Generated entity reducer
import { createReducer, on } from '@ngrx/store';
import * as UserActions from './user.actions';
export const userFeatureKey = 'user';
export const userReducer = createReducer(
initialState,
// Load all entities
on(UserActions.loadUsers, (state) => ({
...state,
loading: true,
error: null
})),
on(UserActions.loadUsersSuccess, (state, { users }) =>
userAdapter.setAll(users, {
...state,
loading: false,
error: null
})
),
on(UserActions.loadUsersFailure, (state, { error }) => ({
...state,
loading: false,
error
})),
// Load single entity
on(UserActions.loadUserSuccess, (state, { user }) =>
userAdapter.upsertOne(user, state)
),
// Add entity
on(UserActions.addUserSuccess, (state, { user }) =>
userAdapter.addOne(user, state)
),
// Update entity
on(UserActions.updateUserSuccess, (state, { user }) =>
userAdapter.updateOne(user, state)
),
// Delete entity
on(UserActions.deleteUserSuccess, (state, { id }) =>
userAdapter.removeOne(id, state)
),
// Selection
on(UserActions.selectUser, (state, { userId }) => ({
...state,
selectedUserId: userId
})),
on(UserActions.clearSelection, (state) => ({
...state,
selectedUserId: null
}))
);Generates comprehensive selectors using EntityAdapter selector methods:
// Generated entity selectors
import { createFeatureSelector, createSelector } from '@ngrx/store';
export const selectUserState = createFeatureSelector<UserState>('user');
// Entity adapter selectors
export const {
selectIds: selectUserIds,
selectEntities: selectUserEntities,
selectAll: selectAllUsers,
selectTotal: selectUserTotal
} = userAdapter.getSelectors(selectUserState);
// Additional selectors
export const selectUsersLoading = createSelector(
selectUserState,
(state: UserState) => state.loading
);
export const selectUsersError = createSelector(
selectUserState,
(state: UserState) => state.error
);
export const selectSelectedUserId = createSelector(
selectUserState,
(state: UserState) => state.selectedUserId
);
export const selectSelectedUser = createSelector(
selectUserEntities,
selectSelectedUserId,
(entities, selectedId) => selectedId ? entities[selectedId] : null
);
// Filtered selectors
export const selectActiveUsers = createSelector(
selectAllUsers,
(users) => users.filter(user => user.active)
);
export const selectUsersByRole = (role: string) => createSelector(
selectAllUsers,
(users) => users.filter(user => user.role === role)
);The generated entity state includes all EntityAdapter methods for entity manipulation:
/**
* EntityAdapter methods available in generated reducer
*/
interface EntityAdapterMethods<T> {
/** Add one entity */
addOne: (entity: T, state: EntityState<T>) => EntityState<T>;
/** Add multiple entities */
addMany: (entities: T[], state: EntityState<T>) => EntityState<T>;
/** Add all entities (replace existing) */
setAll: (entities: T[], state: EntityState<T>) => EntityState<T>;
/** Remove one entity */
removeOne: (id: string, state: EntityState<T>) => EntityState<T>;
/** Remove multiple entities */
removeMany: (ids: string[], state: EntityState<T>) => EntityState<T>;
/** Remove all entities */
removeAll: (state: EntityState<T>) => EntityState<T>;
/** Update one entity */
updateOne: (update: Update<T>, state: EntityState<T>) => EntityState<T>;
/** Update multiple entities */
updateMany: (updates: Update<T>[], state: EntityState<T>) => EntityState<T>;
/** Add or update one entity */
upsertOne: (entity: T, state: EntityState<T>) => EntityState<T>;
/** Add or update multiple entities */
upsertMany: (entities: T[], state: EntityState<T>) => EntityState<T>;
}
/**
* Update interface for partial entity updates
*/
interface Update<T> {
id: string;
changes: Partial<T>;
}Entity adapter supports custom sorting and ID selection:
// Custom entity adapter configuration
export const userAdapter: EntityAdapter<User> = createEntityAdapter<User>({
// Custom ID selector
selectId: (user: User) => user.id,
// Custom sort comparator
sortComparer: (a: User, b: User) => a.name.localeCompare(b.name)
});
// With custom ID field
export interface Product {
productId: string;
name: string;
price: number;
}
export const productAdapter: EntityAdapter<Product> = createEntityAdapter<Product>({
selectId: (product: Product) => product.productId,
sortComparer: (a: Product, b: Product) => a.name.localeCompare(b.name)
});Generates effects for entity CRUD operations:
// Generated entity effects
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { catchError, map, switchMap, mergeMap } from 'rxjs/operators';
import { of } from 'rxjs';
import * as UserActions from './user.actions';
import { UserService } from './user.service';
@Injectable()
export class UserEffects {
loadUsers$ = createEffect(() =>
this.actions$.pipe(
ofType(UserActions.loadUsers),
switchMap(() =>
this.userService.getUsers().pipe(
map(users => UserActions.loadUsersSuccess({ users })),
catchError(error => of(UserActions.loadUsersFailure({ error })))
)
)
)
);
loadUser$ = createEffect(() =>
this.actions$.pipe(
ofType(UserActions.loadUser),
mergeMap(({ id }) =>
this.userService.getUserById(id).pipe(
map(user => UserActions.loadUserSuccess({ user })),
catchError(error => of(UserActions.loadUsersFailure({ error })))
)
)
)
);
addUser$ = createEffect(() =>
this.actions$.pipe(
ofType(UserActions.addUser),
switchMap(({ user }) =>
this.userService.createUser(user).pipe(
map(createdUser => UserActions.addUserSuccess({ user: createdUser })),
catchError(error => of(UserActions.addUserFailure({ error })))
)
)
)
);
updateUser$ = createEffect(() =>
this.actions$.pipe(
ofType(UserActions.updateUser),
switchMap(({ user }) =>
this.userService.updateUser(user).pipe(
map(updatedUser => UserActions.updateUserSuccess({ user: updatedUser })),
catchError(error => of(UserActions.updateUserFailure({ error })))
)
)
)
);
deleteUser$ = createEffect(() =>
this.actions$.pipe(
ofType(UserActions.deleteUser),
switchMap(({ id }) =>
this.userService.deleteUser(id).pipe(
map(() => UserActions.deleteUserSuccess({ id })),
catchError(error => of(UserActions.deleteUserFailure({ error })))
)
)
)
);
constructor(
private actions$: Actions,
private userService: UserService
) {}
}Generated entity state includes comprehensive testing utilities:
// Entity testing examples
describe('User Entity', () => {
const users: User[] = [
{ id: '1', name: 'John', email: 'john@example.com', active: true },
{ id: '2', name: 'Jane', email: 'jane@example.com', active: false }
];
describe('UserReducer', () => {
it('should load users', () => {
const action = UserActions.loadUsersSuccess({ users });
const result = userReducer(initialState, action);
expect(result.ids).toEqual(['1', '2']);
expect(result.entities['1']).toEqual(users[0]);
expect(result.loading).toBe(false);
});
it('should add user', () => {
const newUser: User = { id: '3', name: 'Bob', email: 'bob@example.com', active: true };
const action = UserActions.addUserSuccess({ user: newUser });
const result = userReducer(initialState, action);
expect(result.ids).toContain('3');
expect(result.entities['3']).toEqual(newUser);
});
it('should update user', () => {
const stateWithUsers = userAdapter.setAll(users, initialState);
const update: Update<User> = { id: '1', changes: { name: 'John Updated' } };
const action = UserActions.updateUserSuccess({ user: update });
const result = userReducer(stateWithUsers, action);
expect(result.entities['1']?.name).toBe('John Updated');
});
it('should delete user', () => {
const stateWithUsers = userAdapter.setAll(users, initialState);
const action = UserActions.deleteUserSuccess({ id: '1' });
const result = userReducer(stateWithUsers, action);
expect(result.ids).not.toContain('1');
expect(result.entities['1']).toBeUndefined();
});
});
describe('User Selectors', () => {
const stateWithUsers = userAdapter.setAll(users, {
...initialState,
selectedUserId: '1'
});
it('should select all users', () => {
const result = selectAllUsers.projector(stateWithUsers);
expect(result).toEqual(users);
});
it('should select user entities', () => {
const result = selectUserEntities.projector(stateWithUsers);
expect(result['1']).toEqual(users[0]);
});
it('should select selected user', () => {
const result = selectSelectedUser.projector(stateWithUsers.entities, '1');
expect(result).toEqual(users[0]);
});
});
});Entity state management provides significant performance benefits:
/**
* Performance benefits of entity state
*/
interface EntityPerformanceBenefits {
/** O(1) entity lookups by ID */
fastLookup: 'entities[id] access';
/** Efficient updates without array iteration */
efficientUpdates: 'Direct entity replacement';
/** Normalized data structure */
normalizedData: 'Eliminates data duplication';
/** Memoized selectors */
memoizedSelectors: 'Automatic selector memoization';
/** Optimized change detection */
changeDetection: 'Minimal component re-renders';
}