All skills
Skillintermediate
NgRx State Management
```typescript // app.config.ts import { provideStore } from '@ngrx/store'; import { provideEffects } from '@ngrx/effects'; import { provideStoreDevtools } from '@ngrx/store-devtools';
Claude Code Knowledge Pack7/10/2026
Overview
NgRx State Management
Store Setup
// app.config.ts
providers: [
provideStore({
users: usersReducer,
products: productsReducer
}),
provideEffects([UsersEffects, ProductsEffects]),
provideStoreDevtools({
maxAge: 25,
logOnly: !isDevMode()
})
]
};
Actions (Modern)
// users.actions.ts
source: 'Users',
events: {
'Load Users': emptyProps(),
'Load Users Success': props<{ users: User[] }>(),
'Load Users Failure': props<{ error: string }>(),
'Add User': props<{ user: User }>(),
'Add User Success': props<{ user: User }>(),
'Add User Failure': props<{ error: string }>(),
'Update User': props<{ id: string; changes: Partial }>(),
'Update User Success': props<{ user: User }>(),
'Delete User': props<{ id: string }>(),
'Delete User Success': props<{ id: string }>()
}
});
Reducer with Entity Adapter
// users.reducer.ts
loading: boolean;
error: string | null;
selectedUserId: string | null;
}
selectId: (user: User) => user.id,
sortComparer: (a, b) => a.name.localeCompare(b.name)
});
const initialState: UsersState = usersAdapter.getInitialState({
loading: false,
error: null,
selectedUserId: null
});
initialState,
// Load users
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
})),
// Add user
on(UsersActions.addUserSuccess, (state, { user }) =>
usersAdapter.addOne(user, state)
),
// Update user
on(UsersActions.updateUserSuccess, (state, { user }) =>
usersAdapter.updateOne(
{ id: user.id, changes: user },
state
)
),
// Delete user
on(UsersActions.deleteUserSuccess, (state, { id }) =>
usersAdapter.removeOne(id, state)
)
);
Selectors
// users.selectors.ts
// Entity adapter selectors
const {
selectIds,
selectEntities,
selectAll,
selectTotal
} = usersAdapter.getSelectors();
selectUsersState,
selectIds
);
selectUsersState,
selectEntities
);
selectUsersState,
selectAll
);
selectUsersState,
selectTotal
);
selectUsersState,
(state) => state.loading
);
selectUsersState,
(state) => state.error
);
// Parameterized selector
createSelector(
selectUserEntities,
(entities) => entities[id]
);
// Composed selector
selectAllUsers,
(users) => users.filter(user => user.isActive)
);
// Selector with multiple inputs
selectUserById,
selectAllPosts,
(user, posts) => ({
user,
posts: posts.filter(post => post.userId === user?.id)
})
);
Effects
// users.effects.ts
@Injectable()
private actions$ = inject(Actions);
private usersService = inject(UsersService);
// Load users effect
loadUsers$ = createEffect(() =>
this.actions$.pipe(
ofType(UsersActions.loadUsers),
mergeMap(() =>
this.usersService.getAll().pipe(
map(users => UsersActions.loadUsersSuccess({ users })),
catchError(error =>
of(UsersActions.loadUsersFailure({ error: error.message }))
)
)
)
)
);
// Add user effect (exhaustMap prevents duplicate submits)
addUser$ = createEffect(() =>
this.actions$.pipe(
ofType(UsersActions.addUser),
exhaustMap(({ user }) =>
this.usersService.create(user).pipe(
map(createdUser => UsersActions.addUserSuccess({ user: createdUser })),
catchError(error =>
of(UsersActions.addUserFailure({ error: error.message }))
)
)
)
)
);
// Update user effect
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.loadUsersFailure({ error: error.message }))
)
)
)
)
);
// Delete user effect
deleteUser$ = createEffect(() =>
this.actions$.pipe(
ofType(UsersActions.deleteUser),
mergeMap(({ id }) =>
this.usersService.delete(id).pipe(
map(() => UsersActions.deleteUserSuccess({ id })),
catchError(error =>
of(UsersActions.loadUsersFailure({ error: error.message }))
)
)
)
)
);
// Non-dispatching effect (side effect only)
logUserActions$ = createEffect(
() =>
this.actions$.pipe(
ofType(
UsersActions.addUserSuccess,
UsersActions.updateUserSuccess,
UsersActions.deleteUserSuccess
),
tap(action => console.log('User action:', action))
),
{ dispatch: false }
);
}
Component Integration
// users-list.component.ts
selectAllUsers,
selectUsersLoading,
selectUsersError
} from './store/users.selectors';
@Component({
selector: 'app-users-list',
standalone: true,
template: `
@if (loading()) {
<div>Loading...</div>
} @else if (error(); as err) {
<div>Error: {{ err }}</div>
} @else {
@for (user of users(); track user.id) {
<div>
{{ user.name }}
<button (click)="onDelete(user.id)">Delete</button>
</div>
}
}
`
})
private store = inject(Store);
// Select data as signals
users = toSignal(this.store.select(selectAllUsers), { initialValue: [] });
loading = toSignal(this.store.select(selectUsersLoading), { initialValue: false });
error = toSignal(this.store.select(selectUsersError), { initialValue: null });
ngOnInit() {
this.store.dispatch(UsersActions.loadUsers());
}
onDelete(id: string) {
this.store.dispatch(UsersActions.deleteUser({ id }));
}
}
Facade Pattern
// users.facade.ts
@Injectable({ providedIn: 'root' })
private store = inject(Store);
// Selectors
users$ = this.store.select(selectAllUsers);
loading$ = this.store.select(selectUsersLoading);
error$ = this.store.select(selectUsersError);
// Actions
loadUsers() {
this.store.dispatch(UsersActions.loadUsers());
}
addUser(user: User) {
this.store.dispatch(UsersActions.addUser({ user }));
}
updateUser(id: string, changes: Partial) {
this.store.dispatch(UsersActions.updateUser({ id, changes }));
}
deleteUser(id: string) {
this.store.dispatch(UsersActions.deleteUser({ id }));
}
getUserById(id: string) {
return this.store.select(selectUserById(id));
}
}
// Usage in component
@Component({
selector: 'app-users',
standalone: true
})
private facade = inject(UsersFacade);
users = toSignal(this.facade.users$, { initialValue: [] });
loading = toSignal(this.facade.loading$, { initialValue: false });
ngOnInit() {
this.facade.loadUsers();
}
onAdd(user: User) {
this.facade.addUser(user);
}
}
Quick Reference
| Concept | Usage |
|---|---|
| Actions | createActionGroup() |
| Reducer | createReducer(), on() |
| Entity | createEntityAdapter() |
| Selectors | createSelector(), createFeatureSelector() |
| Effects | createEffect(), ofType() |
| Store | inject(Store), store.select(), store.dispatch() |
| DevTools | provideStoreDevtools() |
| Testing | Mock store, marble testing |