NgRx状态管理 #

一、NgRx概述 #

NgRx是基于Redux模式的状态管理库,用于管理Angular应用的状态。

text
┌─────────────────────────────────────────────────────────────┐
│                    NgRx 数据流                               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Component → dispatch → Action → Reducer → Store           │
│      ↑                                          ↓           │
│      └─────────────── select ──────────────────┘           │
│                                                             │
│  Component → dispatch → Action → Effects → Action          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

二、核心概念 #

概念 说明
Store 状态存储
State 应用状态
Actions 描述发生的事件
Reducers 纯函数,处理状态变化
Selectors 查询状态的纯函数
Effects 处理副作用

三、安装NgRx #

bash
npm install @ngrx/store @ngrx/effects @ngrx/store-devtools

四、定义状态 #

4.1 状态接口 #

typescript
export interface User {
  id: number;
  name: string;
  email: string;
}

export interface UserState {
  users: User[];
  selectedUserId: number | null;
  loading: boolean;
  error: string | null;
}

export const initialUserState: UserState = {
  users: [],
  selectedUserId: null,
  loading: false,
  error: null
};

4.2 根状态 #

typescript
export interface AppState {
  users: UserState;
  products: ProductState;
}

五、Actions #

5.1 创建Actions #

typescript
import { createAction, props } from '@ngrx/store';

export const loadUsers = createAction('[User] Load Users');
export const loadUsersSuccess = createAction('[User] Load Users Success', props<{ users: User[] }>());
export const loadUsersFailure = createAction('[User] Load Users Failure', props<{ error: string }>());

export const selectUser = createAction('[User] Select User', props<{ userId: number }>());

export const addUser = createAction('[User] Add User', props<{ user: User }>());
export const updateUser = createAction('[User] Update User', props<{ user: User }>());
export const deleteUser = createAction('[User] Delete User', props<{ userId: number }>());

5.2 createActionGroup #

typescript
import { createActionGroup, props, emptyProps } from '@ngrx/store';

export const UserActions = createActionGroup({
  source: 'User',
  events: {
    'Load Users': emptyProps(),
    'Load Users Success': props<{ users: User[] }>(),
    'Load Users Failure': props<{ error: string }>(),
    'Select User': props<{ userId: number }>(),
    'Add User': props<{ user: User }>(),
    'Update User': props<{ user: User }>(),
    'Delete User': props<{ userId: number }>()
  }
});

六、Reducers #

6.1 创建Reducer #

typescript
import { createReducer, on } from '@ngrx/store';
import { UserActions } from './user.actions';
import { initialUserState, UserState } from './user.state';

export const userReducer = createReducer(
  initialUserState,
  
  on(UserActions.loadUsers, (state) => ({
    ...state,
    loading: true,
    error: null
  })),
  
  on(UserActions.loadUsersSuccess, (state, { users }) => ({
    ...state,
    users,
    loading: false
  })),
  
  on(UserActions.loadUsersFailure, (state, { error }) => ({
    ...state,
    loading: false,
    error
  })),
  
  on(UserActions.selectUser, (state, { userId }) => ({
    ...state,
    selectedUserId: userId
  })),
  
  on(UserActions.addUser, (state, { user }) => ({
    ...state,
    users: [...state.users, user]
  })),
  
  on(UserActions.updateUser, (state, { user }) => ({
    ...state,
    users: state.users.map(u => u.id === user.id ? user : u)
  })),
  
  on(UserActions.deleteUser, (state, { userId }) => ({
    ...state,
    users: state.users.filter(u => u.id !== userId)
  }))
);

七、Selectors #

7.1 创建Selectors #

typescript
import { createFeatureSelector, createSelector } from '@ngrx/store';
import { UserState } from './user.state';

export const selectUserState = createFeatureSelector<UserState>('users');

export const selectUsers = createSelector(
  selectUserState,
  (state: UserState) => state.users
);

export const selectLoading = createSelector(
  selectUserState,
  (state: UserState) => state.loading
);

export const selectError = createSelector(
  selectUserState,
  (state: UserState) => state.error
);

export const selectSelectedUserId = createSelector(
  selectUserState,
  (state: UserState) => state.selectedUserId
);

export const selectSelectedUser = createSelector(
  selectUsers,
  selectSelectedUserId,
  (users, selectedUserId) => users.find(u => u.id === selectedUserId) || null
);

export const selectUserById = (id: number) => createSelector(
  selectUsers,
  (users) => users.find(u => u.id === id)
);

八、Effects #

8.1 创建Effects #

typescript
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { UserService } from '../services/user.service';
import { UserActions } from './user.actions';
import { catchError, map, mergeMap, switchMap } from 'rxjs/operators';
import { of } from 'rxjs';

@Injectable()
export class UserEffects {
  constructor(
    private actions$: Actions,
    private userService: UserService
  ) {}
  
  loadUsers$ = createEffect(() =>
    this.actions$.pipe(
      ofType(UserActions.loadUsers),
      switchMap(() =>
        this.userService.getUsers().pipe(
          map(users => UserActions.loadUsersSuccess({ users })),
          catchError(error => of(UserActions.loadUsersFailure({ error: error.message })))
        )
      )
    )
  );
  
  addUser$ = createEffect(() =>
    this.actions$.pipe(
      ofType(UserActions.addUser),
      mergeMap(({ user }) =>
        this.userService.createUser(user).pipe(
          map(createdUser => UserActions.addUserSuccess({ user: createdUser })),
          catchError(error => of(UserActions.addUserFailure({ error: error.message })))
        )
      )
    )
  );
  
  updateUser$ = createEffect(() =>
    this.actions$.pipe(
      ofType(UserActions.updateUser),
      mergeMap(({ user }) =>
        this.userService.updateUser(user).pipe(
          map(updatedUser => UserActions.updateUserSuccess({ user: updatedUser })),
          catchError(error => of(UserActions.updateUserFailure({ error: error.message })))
        )
      )
    )
  );
  
  deleteUser$ = createEffect(() =>
    this.actions$.pipe(
      ofType(UserActions.deleteUser),
      mergeMap(({ userId }) =>
        this.userService.deleteUser(userId).pipe(
          map(() => UserActions.deleteUserSuccess({ userId })),
          catchError(error => of(UserActions.deleteUserFailure({ error: error.message })))
        )
      )
    )
  );
}

九、注册Store #

9.1 提供Store #

typescript
import { provideStore } from '@ngrx/store';
import { provideEffects } from '@ngrx/effects';
import { provideStoreDevtools } from '@ngrx/store-devtools';
import { userReducer } from './store/user.reducer';
import { UserEffects } from './store/user.effects';

export const appConfig: ApplicationConfig = {
  providers: [
    provideStore({
      users: userReducer
    }),
    provideEffects([UserEffects]),
    provideStoreDevtools({
      maxAge: 25,
      logOnly: !isDevMode()
    })
  ]
};

十、在组件中使用 #

10.1 读取状态 #

typescript
import { Component, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { UserActions } from '../store/user.actions';
import { selectUsers, selectLoading, selectError } from '../store/user.selectors';

@Component({
  selector: 'app-user-list',
  template: `
    <div *ngIf="loading$ | async">加载中...</div>
    <div *ngIf="error$ | async as error" class="error">{{ error }}</div>
    
    <ul>
      <li *ngFor="let user of users$ | async">
        {{ user.name }} - {{ user.email }}
      </li>
    </ul>
  `
})
export class UserListComponent implements OnInit {
  users$: Observable<User[]>;
  loading$: Observable<boolean>;
  error$: Observable<string | null>;
  
  constructor(private store: Store) {
    this.users$ = this.store.select(selectUsers);
    this.loading$ = this.store.select(selectLoading);
    this.error$ = this.store.select(selectError);
  }
  
  ngOnInit() {
    this.store.dispatch(UserActions.loadUsers());
  }
}

10.2 派发Actions #

typescript
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { UserActions } from '../store/user.actions';

@Component({
  selector: 'app-user-form',
  template: `
    <form (ngSubmit)="onSubmit()">
      <input [(ngModel)]="user.name" name="name" />
      <input [(ngModel)]="user.email" name="email" />
      <button type="submit">添加用户</button>
    </form>
  `
})
export class UserFormComponent {
  user = { name: '', email: '' };
  
  constructor(private store: Store) {}
  
  onSubmit() {
    this.store.dispatch(UserActions.addUser({
      user: {
        id: Date.now(),
        ...this.user
      }
    }));
    this.user = { name: '', email: '' };
  }
}

10.3 使用信号(Angular 17+) #

typescript
import { Component, inject } from '@angular/core';
import { Store } from '@ngrx/store';
import { toSignal } from '@angular/core/rxjs-interop';
import { UserActions } from '../store/user.actions';
import { selectUsers, selectLoading } from '../store/user.selectors';

@Component({
  selector: 'app-user-list',
  template: `
    <div *ngIf="loading()">加载中...</div>
    <ul>
      <li *ngFor="let user of users()">
        {{ user.name }}
      </li>
    </ul>
  `
})
export class UserListComponent {
  private store = inject(Store);
  
  users = toSignal(this.store.select(selectUsers), { initialValue: [] });
  loading = toSignal(this.store.select(selectLoading), { initialValue: false });
  
  constructor() {
    this.store.dispatch(UserActions.loadUsers());
  }
}

十一、完整示例 #

11.1 文件结构 #

text
store/
├── user/
│   ├── user.actions.ts
│   ├── user.reducer.ts
│   ├── user.selectors.ts
│   ├── user.effects.ts
│   └── user.state.ts
└── index.ts

11.2 完整状态管理 #

typescript
// user.state.ts
export interface UserState {
  users: User[];
  loading: boolean;
  error: string | null;
}

export const initialUserState: UserState = {
  users: [],
  loading: false,
  error: null
};

// user.actions.ts
export const UserActions = createActionGroup({
  source: 'User',
  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<{ user: User }>(),
    'Update User Success': props<{ user: User }>(),
    'Update User Failure': props<{ error: string }>(),
    'Delete User': props<{ userId: number }>(),
    'Delete User Success': props<{ userId: number }>(),
    'Delete User Failure': props<{ error: string }>()
  }
});

// user.reducer.ts
export const userReducer = createReducer(
  initialUserState,
  on(UserActions.loadUsers, (state) => ({
    ...state,
    loading: true,
    error: null
  })),
  on(UserActions.loadUsersSuccess, (state, { users }) => ({
    ...state,
    users,
    loading: false
  })),
  on(UserActions.loadUsersFailure, (state, { error }) => ({
    ...state,
    loading: false,
    error
  })),
  on(UserActions.addUserSuccess, (state, { user }) => ({
    ...state,
    users: [...state.users, user]
  })),
  on(UserActions.updateUserSuccess, (state, { user }) => ({
    ...state,
    users: state.users.map(u => u.id === user.id ? user : u)
  })),
  on(UserActions.deleteUserSuccess, (state, { userId }) => ({
    ...state,
    users: state.users.filter(u => u.id !== userId)
  }))
);

// user.selectors.ts
export const selectUserState = createFeatureSelector<UserState>('users');

export const selectAllUsers = createSelector(
  selectUserState,
  (state) => state.users
);

export const selectUserLoading = createSelector(
  selectUserState,
  (state) => state.loading
);

export const selectUserError = createSelector(
  selectUserState,
  (state) => state.error
);

// user.effects.ts
@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: error.message })))
        )
      )
    )
  );
  
  constructor(
    private actions$: Actions,
    private userService: UserService
  ) {}
}

十二、总结 #

概念 说明
Store 状态存储
State 应用状态
Actions 描述事件
Reducers 处理状态变化
Selectors 查询状态
Effects 处理副作用

下一步:管道

最后更新:2026-03-26