名称: ngrx 描述: 用于Angular的NgRx状态管理,包括存储、效果、实体适配器、组件存储和选择器。 允许工具: 读取、写入、编辑、Bash、Glob、Grep
NgRx 技能
为Angular应用程序实现NgRx状态管理的专家级协助。
能力
- 配置具有功能状态的NgRx存储
- 创建动作、减速器和选择器
- 实现副作用的效果
- 为集合使用实体适配器
- 为本地状态应用组件存储
- 设置NgRx DevTools集成
使用场景
在以下情况下调用此技能:
- 实现集中式状态管理
- 处理复杂的异步工作流
- 管理规范化的实体集合
- 使用DevTools调试状态变更
- 扩展Angular应用程序状态
输入参数
| 参数 | 类型 | 是否必需 | 描述 |
|---|---|---|---|
| 功能名称 | 字符串 | 是 | 功能状态名称 |
| 实体适配器 | 布尔值 | 否 | 使用实体适配器 |
| 效果 | 数组 | 否 | 要创建的效果 |
| 组件存储 | 布尔值 | 否 | 使用组件存储 |
配置示例
{
"功能名称": "用户",
"实体适配器": true,
"效果": ["加载用户", "创建用户", "更新用户"],
"组件存储": false
}
NgRx 模式
动作
// store/users/users.actions.ts
import { createActionGroup, emptyProps, props } from '@ngrx/store';
export const UsersActions = createActionGroup({
source: 'Users',
events: {
'加载用户': emptyProps(),
'加载用户成功': props<{ users: User[] }>(),
'加载用户失败': props<{ error: string }>(),
'创建用户': props<{ user: CreateUserDto }>(),
'创建用户成功': props<{ user: User }>(),
'创建用户失败': props<{ error: string }>(),
'更新用户': props<{ id: string; changes: Partial<User> }>(),
'更新用户成功': props<{ user: User }>(),
'更新用户失败': props<{ error: string }>(),
'删除用户': props<{ id: string }>(),
'删除用户成功': props<{ id: string }>(),
'删除用户失败': props<{ error: string }>(),
'选择用户': props<{ id: string | null }>(),
},
});
使用实体适配器的减速器
// 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,
}))
);
选择器
// 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);
效果
// 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 }))
)
)
)
)
);
}
组件存储(本地状态)
// 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: '',
});
}
// 选择器
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()))
);
// 更新器
readonly setFilter = this.updater((state, filter: string) => ({
...state,
filter,
}));
// 效果
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;
})
)
)
)
);
}
存储设置
// 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,
}),
],
};
最佳实践
- 使用 createActionGroup 创建相关动作
- 利用实体适配器处理集合
- 保持效果专注于单一职责
- 为本地状态使用组件存储
- 创建细粒度选择器以提升性能
目标流程
- angular-enterprise-development
- state-management-setup
- complex-data-flows
- enterprise-architecture