CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-ngrx--schematics

Angular CLI schematics for generating NgRx state management code including actions, reducers, effects, selectors, and feature modules.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

entity-management.mddocs/

Entity Management

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.

Capabilities

Entity Schematic

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;
}

Entity State Structure

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.ts

Entity Actions

Generates 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');

Entity Reducer

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
  }))
);

Entity Selectors

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)
);

Entity Adapter Methods

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>;
}

Custom Sort and ID Selection

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)
});

Entity Effects

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
  ) {}
}

Entity Testing

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]);
    });
  });
});

Performance Benefits

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';
}

docs

action-generation.md

component-store.md

container-components.md

data-services.md

effect-generation.md

entity-management.md

feature-generation.md

index.md

ngrx-push-migration.md

reducer-generation.md

selector-generation.md

store-setup.md

utility-functions.md

tile.json