Babysitter ngrx
NgRx state management for Angular including store, effects, entity adapter, component store, and selectors.
install
source · Clone the upstream repo
git clone https://github.com/a5c-ai/babysitter
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/a5c-ai/babysitter "$T" && mkdir -p ~/.claude/skills && cp -r "$T/library/specializations/web-development/skills/ngrx" ~/.claude/skills/a5c-ai-babysitter-ngrx && rm -rf "$T"
manifest:
library/specializations/web-development/skills/ngrx/SKILL.mdsource content
NgRx Skill
Expert assistance for implementing NgRx state management in Angular applications.
Capabilities
- Configure NgRx store with feature states
- Create actions, reducers, and selectors
- Implement effects for side effects
- Use entity adapter for collections
- Apply component store for local state
- Set up NgRx DevTools integration
Usage
Invoke this skill when you need to:
- Implement centralized state management
- Handle complex async workflows
- Manage normalized entity collections
- Debug state changes with DevTools
- Scale Angular application state
Inputs
| Parameter | Type | Required | Description |
|---|---|---|---|
| featureName | string | Yes | Feature state name |
| entityAdapter | boolean | No | Use entity adapter |
| effects | array | No | Effects to create |
| componentStore | boolean | No | Use component store |
Configuration Example
{ "featureName": "users", "entityAdapter": true, "effects": ["loadUsers", "createUser", "updateUser"], "componentStore": false }
NgRx Patterns
Actions
// store/users/users.actions.ts import { createActionGroup, emptyProps, props } from '@ngrx/store'; export const UsersActions = createActionGroup({ source: 'Users', events: { 'Load Users': emptyProps(), 'Load Users Success': props<{ users: User[] }>(), 'Load Users Failure': props<{ error: string }>(), 'Create User': props<{ user: CreateUserDto }>(), 'Create User Success': props<{ user: User }>(), 'Create User Failure': props<{ error: string }>(), 'Update User': props<{ id: string; changes: Partial<User> }>(), 'Update User Success': props<{ user: User }>(), 'Update User Failure': props<{ error: string }>(), 'Delete User': props<{ id: string }>(), 'Delete User Success': props<{ id: string }>(), 'Delete User Failure': props<{ error: string }>(), 'Select User': props<{ id: string | null }>(), }, });
Reducer with Entity Adapter
// store/users/users.reducer.ts import { createReducer, on } from '@ngrx/store'; import { createEntityAdapter, EntityState } from '@ngrx/entity'; import { UsersActions } from './users.actions'; export interface User { id: string; name: string; email: string; } export interface UsersState extends EntityState<User> { selectedId: string | null; loading: boolean; error: string | null; } export const usersAdapter = createEntityAdapter<User>({ selectId: (user) => user.id, sortComparer: (a, b) => a.name.localeCompare(b.name), }); const initialState: UsersState = usersAdapter.getInitialState({ selectedId: null, loading: false, error: null, }); export const usersReducer = createReducer( initialState, on(UsersActions.loadUsers, (state) => ({ ...state, loading: true, error: null, })), on(UsersActions.loadUsersSuccess, (state, { users }) => usersAdapter.setAll(users, { ...state, loading: false }) ), on(UsersActions.loadUsersFailure, (state, { error }) => ({ ...state, loading: false, error, })), on(UsersActions.createUserSuccess, (state, { user }) => usersAdapter.addOne(user, state) ), on(UsersActions.updateUserSuccess, (state, { user }) => usersAdapter.updateOne({ id: user.id, changes: user }, state) ), on(UsersActions.deleteUserSuccess, (state, { id }) => usersAdapter.removeOne(id, state) ), on(UsersActions.selectUser, (state, { id }) => ({ ...state, selectedId: id, })) );
Selectors
// store/users/users.selectors.ts import { createFeatureSelector, createSelector } from '@ngrx/store'; import { usersAdapter, UsersState } from './users.reducer'; export const selectUsersState = createFeatureSelector<UsersState>('users'); const { selectAll, selectEntities, selectIds, selectTotal } = usersAdapter.getSelectors(); export const selectAllUsers = createSelector(selectUsersState, selectAll); export const selectUserEntities = createSelector( selectUsersState, selectEntities ); export const selectUsersLoading = createSelector( selectUsersState, (state) => state.loading ); export const selectUsersError = createSelector( selectUsersState, (state) => state.error ); export const selectSelectedUserId = createSelector( selectUsersState, (state) => state.selectedId ); export const selectSelectedUser = createSelector( selectUserEntities, selectSelectedUserId, (entities, selectedId) => (selectedId ? entities[selectedId] : null) ); export const selectUsersCount = createSelector(selectUsersState, selectTotal);
Effects
// store/users/users.effects.ts import { Injectable, inject } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { catchError, map, mergeMap, switchMap, of } from 'rxjs'; import { UsersActions } from './users.actions'; import { UsersService } from '../../services/users.service'; @Injectable() export class UsersEffects { private actions$ = inject(Actions); private usersService = inject(UsersService); loadUsers$ = createEffect(() => this.actions$.pipe( ofType(UsersActions.loadUsers), switchMap(() => this.usersService.getAll().pipe( map((users) => UsersActions.loadUsersSuccess({ users })), catchError((error) => of(UsersActions.loadUsersFailure({ error: error.message })) ) ) ) ) ); createUser$ = createEffect(() => this.actions$.pipe( ofType(UsersActions.createUser), mergeMap(({ user }) => this.usersService.create(user).pipe( map((user) => UsersActions.createUserSuccess({ user })), catchError((error) => of(UsersActions.createUserFailure({ error: error.message })) ) ) ) ) ); updateUser$ = createEffect(() => this.actions$.pipe( ofType(UsersActions.updateUser), mergeMap(({ id, changes }) => this.usersService.update(id, changes).pipe( map((user) => UsersActions.updateUserSuccess({ user })), catchError((error) => of(UsersActions.updateUserFailure({ error: error.message })) ) ) ) ) ); deleteUser$ = createEffect(() => this.actions$.pipe( ofType(UsersActions.deleteUser), mergeMap(({ id }) => this.usersService.delete(id).pipe( map(() => UsersActions.deleteUserSuccess({ id })), catchError((error) => of(UsersActions.deleteUserFailure({ error: error.message })) ) ) ) ) ); }
Component Store (Local State)
// components/user-list/user-list.store.ts import { Injectable } from '@angular/core'; import { ComponentStore } from '@ngrx/component-store'; import { switchMap, tap, catchError, EMPTY } from 'rxjs'; interface UserListState { users: User[]; loading: boolean; filter: string; } @Injectable() export class UserListStore extends ComponentStore<UserListState> { constructor(private usersService: UsersService) { super({ users: [], loading: false, filter: '', }); } // Selectors readonly users$ = this.select((state) => state.users); readonly loading$ = this.select((state) => state.loading); readonly filter$ = this.select((state) => state.filter); readonly filteredUsers$ = this.select( this.users$, this.filter$, (users, filter) => users.filter((u) => u.name.toLowerCase().includes(filter.toLowerCase())) ); // Updaters readonly setFilter = this.updater((state, filter: string) => ({ ...state, filter, })); // Effects readonly loadUsers = this.effect((trigger$) => trigger$.pipe( tap(() => this.patchState({ loading: true })), switchMap(() => this.usersService.getAll().pipe( tap((users) => this.patchState({ users, loading: false })), catchError(() => { this.patchState({ loading: false }); return EMPTY; }) ) ) ) ); }
Store Setup
// app.config.ts import { ApplicationConfig } from '@angular/core'; import { provideStore } from '@ngrx/store'; import { provideEffects } from '@ngrx/effects'; import { provideStoreDevtools } from '@ngrx/store-devtools'; import { usersReducer } from './store/users/users.reducer'; import { UsersEffects } from './store/users/users.effects'; export const appConfig: ApplicationConfig = { providers: [ provideStore({ users: usersReducer, }), provideEffects(UsersEffects), provideStoreDevtools({ maxAge: 25, logOnly: false, }), ], };
Best Practices
- Use createActionGroup for related actions
- Leverage entity adapter for collections
- Keep effects focused on single responsibilities
- Use component store for local state
- Create granular selectors for performance
Target Processes
- angular-enterprise-development
- state-management-setup
- complex-data-flows
- enterprise-architecture