前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Angular 接入 NGRX 状态管理

Angular 接入 NGRX 状态管理

作者头像
前端小鑫同学
发布2024-01-26 09:28:00
1710
发布2024-01-26 09:28:00
举报
Untitled.png
Untitled.png

注:图片来自ngrx.io/guide/store

NGRX 是 Angular 实现响应式状态管理的应用框架。

NGRX 状态管理生命周期图中包含了以下元素:

  1. Store:集中的状态存储;
  2. Action:根据用户所触的不同事件执行不同的 Action ;
  3. Reducer:根据不同的 Action 对 Store 中存储的状态做出相应的改变;
  4. Selector:用于获取存储状态切片的纯函数;
  5. Effects:基于流实现的副作用的处理,以减少基于外部交互的状态。

NGRX 状态管理中包含了两条变更状态的主线:

  1. 同步变更状态:用户 => Action => Reducer => Store(State)
  2. 异步变更状态:用户 => Action => Effects => Service => Effects => Action => Reducer => Store(State)

快速开始

创建 Angular 项目:

安装并执行 CLI 创建 Angular 项目

代码语言:javascript
复制
# 基于 Angular 17 版本演示
# 注意要将 Nodejs 版本切换至 18.13+
npm install -g @angular/cli

# 创建为 standalone 类型的项目
ng new angular-ngrx --standalone=false

安装 NGRX 核心模块:

  1. @ngrx/store:状态管理核心模块,包含了状态存储、Actions、Reducers、Selectors;
  2. @ngrx/store-devtools:调试的工具,需要配合github.com/reduxjs/red… 使用;
  3. @ngrx/schematics:提供使用 NGRX 的 CLI 命令,需要与 Angular 进行整合使用;

安装命令:

代码语言:javascript
复制
npm install @ngrx/store --save
npm install @ngrx/store-devtools --save
npm install @ngrx/schematics --save-dev

更新 angular.json:

代码语言:javascript
复制
{
    "cli": {
        "schematicCollections": ["@ngrx/schematics"]
    }
}

创建存储 State 的 Store:

选项介绍:

选项

作用

--root

目标模块为根模块时设置

--module

提供目标模块的路径

--state-path

提供 State 存储的路径

--state-interface

提供 State 接口名称

示例命令:

代码语言:javascript
复制
ng generate store State --root --module=app.module.ts --state-path=store --state-interface AppState

生成 app/store/index.ts 并更新了 app.module.ts

代码语言:javascript
复制
import { isDevMode } from '@angular/core';
import {
  ActionReducer,
  ActionReducerMap,
  createFeatureSelector,
  createSelector,
  MetaReducer
} from '@ngrx/store';

export interface AppState {}

export const reducers: ActionReducerMap<AppState> = {};

export const metaReducers: MetaReducer<AppState>[] = isDevMode() ? [] : [];
代码语言:javascript
复制
@NgModule({
  imports: [
    ...
    StoreModule.forRoot(reducers, { metaReducers }),
    StoreDevtoolsModule.instrument(),
  ],
	...
})
export class AppModule {}

创建用于添加和删除用户的 Action:

示例命令:

代码语言:javascript
复制
ng generate action store/actions/user

正生成的 app/store/actions/user.actions.ts 模版代码中作以下更改:

代码语言:javascript
复制
import { createActionGroup, emptyProps, props } from '@ngrx/store';

export const UserActions = createActionGroup({
  source: 'User',
  events: {
    AddUser: props<{ name: string; age: number; gender: string }>(),
    DelUser: emptyProps(),
  },
});
  1. 增加用于添加用户的AddUser ,并使用 props 约束所接收的参数类型;
  2. 增加用于删除用户的DelUser,并使用emptyProps表示不传递任何参数(仅存储一位用户);

创建根据 Action 来更新状态的 Reducer:

选项介绍:

选项

作用

--reducers

执行reducers存放路径,约定路径为上一级的 index.ts,也是 store 创建的文件

--skip-tests

跳过生成测试文件

示例命令:

代码语言:javascript
复制
ng generate reducer store/reducers/user --reducers=../index.ts --skip-tests

生成 app/store/reducers/user.reducer.ts 并更新 app/store/index.ts

代码语言:javascript
复制
import { createReducer, on } from '@ngrx/store';
import { UserActions } from './user.actions';

export const userFeatureKey = 'user';

export interface State {}

export const initialState: State = {};

export const reducer = createReducer(
  initialState,
);
代码语言:javascript
复制
import { isDevMode } from '@angular/core';
import {
  ActionReducer,
  ActionReducerMap,
  createFeatureSelector,
  createSelector,
  MetaReducer
} from '@ngrx/store';
import * as fromUser from './reducers/user.reducer';

export interface AppState {
  [fromUser.userFeatureKey]: fromUser.State;
}

export const reducers: ActionReducerMap<AppState> = {
  [fromUser.userFeatureKey]: fromUser.reducer,
};

export const metaReducers: MetaReducer<AppState>[] = isDevMode() ? [] : [];

添加核心更改状态的代码到 app/store/reducers/user.reducer.ts

代码语言:javascript
复制
import { createReducer, on } from '@ngrx/store';
import { UserActions } from '../actions/user.actions';

export const userFeatureKey = 'user';

// 定义 State 接口
export interface State {
  id: string;
  name: string;
  age: number;
  gender: string;
}

// 申明 State 的初始状态
export const initialState: State = {
  id: '',
  name: '',
  age: 0,
  gender: '',
};

export const reducer = createReducer(
  initialState,
	// 监听 UserActions 中的 addUser 事件并更新状态
  on(UserActions.addUser, (state, action) => ({
    id: '',
    name: action.name,
    age: action.age,
    gender: action.gender,
  })),
	// 监听 UserActions 中的 delUser 事件并更新状态
  on(UserActions.delUser, (state, action) => ({
    id: '',
    name: '',
    age: 0,
    gender: '',
  }))
);

创建获取状态的使用的 Selector:

示例命令:

代码语言:javascript
复制
ng generate selector store/selectors/user --skip-tests

生成的 app/store/selectors/user.selectors.ts 仅包含导入模块的一行代码:

代码语言:javascript
复制
import { createFeatureSelector, createSelector } from '@ngrx/store';

使用导入的函数创建适用于 User 的 Selector:

代码语言:javascript
复制
import { createFeatureSelector, createSelector } from '@ngrx/store';
import { State, userFeatureKey } from '../reducers/user.reducer';

/**
 * 用于获取 User
 */
export const selectUser = createFeatureSelector<State>(userFeatureKey);

/**
 * 用于获取 User 的 name
 */
export const selectUserName = createSelector(
  selectUser,
  (state: State) => state.name
);

进入模拟场景:

模拟这样一个场景:在组件加载完成后首先执行添加 User 的 Action,在 5 秒之后执行删除 User 的 Action,用来模拟 User 数据状态的变化,并将 User 绑定到页面用来观察,最后切换不用的 Selector 体验它的作用。

  1. app.component.ts 构造函数中注入 Store:
代码语言:javascript
复制
import { Store } from '@ngrx/store';

export class AppComponent {

	// 注入 Store
  constructor(private store: Store) {}
}
  1. 让根组件实现 OnInit 接口,按模拟场景通过 store 触发 action:
代码语言:javascript
复制
export class AppComponent implements OnInit {
  title = 'angular-ngrx';

  constructor(private store: Store) {}

  ngOnInit(): void {
    // 添加用户
    this.store.dispatch(
      UserActions.addUser({
        name: 'xiao zhang',
        age: 18,
        gender: 'male',
      })
    );

    // 删除用户
    setTimeout(() => {
      this.store.dispatch(UserActions.delUser());
    }, 5000);
  }
}
  1. 定义 User (Observable类型)属性,并通过 selectUser 获取到用户数据状态:
代码语言:javascript
复制
export class AppComponent implements OnInit {
  title = 'angular-ngrx';

  user: Observable<{
    id: string;
    name: string;
    age: number;
    gender: string;
  }>;

  constructor(private store: Store) {
    this.user = this.store.select(selectUser);
  }

  ...
}
  1. 使用管道符在页面渲染 Observable 类型 User:
代码语言:javascript
复制
<div class="content">
    {{ user | async | json }}
</div>

接入副作用

通过接入副作用(effects)来完成异步获取网络数据更新状态。

安装 effects 核心模块:

代码语言:javascript
复制
npm install @ngrx/effects --save

创建 User 的副作用:

选项介绍 :

选项

作用

--root

目标模块为根模块时设置

--module

提供目标模块的路径

--skip-tests

跳过生成测试文件

示例命令:

代码语言:javascript
复制
ng generate effect store/effects/user --root --module=app.module.ts --skip-tests

创建 app/store/effects/user.effects.ts 并更新 app.module.ts

代码语言:javascript
复制
import { Injectable } from '@angular/core';
import { Actions, createEffect } from '@ngrx/effects';

@Injectable()
export class UserEffects {
  constructor(private actions$: Actions) {}
}
代码语言:javascript
复制
import { EffectsModule } from '@ngrx/effects';
import { UserEffects } from './store/effects/user.effects';

@NgModule({
  ...
  imports: [
	  ...
    EffectsModule.forRoot([UserEffects]),
  ],
})
export class AppModule {}

编写 Test User Api:

执行 ng 命令生成 User 服务:

代码语言:javascript
复制
ng g service services/user --skip-tests

编写用来模拟网络获取用户数据的异步函数 updateApi :

代码语言:javascript
复制
import { Injectable } from '@angular/core';
import { Observable, map, timer } from 'rxjs';

@Injectable({
  providedIn: 'root',
})
export class UserService {
  constructor() {}

  updateApi(): Observable<{
    name: string;
    age: number;
    gender: string;
  }> {
    return timer(3000).pipe(
      map(() => ({
        name: 'xiao li',
        age: 23,
        gender: 'male',
      }))
    );
  }
}

添加新的 Actions:

这里的 UpdateUser 同样是 emptyProps,仅作为触发使用,更新用户数据在接下来的副作用编写中会体现:

代码语言:javascript
复制
import { createActionGroup, emptyProps, props } from '@ngrx/store';

export const UserActions = createActionGroup({
  source: 'User',
  events: {
		...
    UpdateUser: emptyProps(),
  },
});

完成副作用编写:

UserEffects 中注入 UserService 后开始创建副作用,总共 4 步操作:

代码语言:javascript
复制
import { UserService } from './../../services/user.service';
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { UserActions } from '../actions/user.actions';
import { exhaustMap, map } from 'rxjs';

@Injectable()
export class UserEffects {
  updateUser$ = createEffect(() => {
    return this.actions$.pipe(
      // 设置副作用所关联的 Action
      ofType(UserActions.updateUser),
      // 处理副作用
      exhaustMap(() => {
        // 调用服务,获取用户数据
        return this.userService.updateApi().pipe(
          map((user) => {
            // 将得到的用户数据通过 AddUser Action 发送出去
            return UserActions.addUser(user);
          })
        );
      })
    );
  });

  constructor(private actions$: Actions, private userService: UserService) {}
}

进入模拟场景:

在组件加载完的 5 秒后,用户数据的状态被清空,紧接着就执行 UpdateUser Action,来获取网络上的用户数据:

代码语言:javascript
复制
export class AppComponent implements OnInit {
  ...

  ngOnInit(): void {
    // 添加用户
    this.store.dispatch(
      UserActions.addUser({
        name: 'xiao zhang',
        age: 18,
        gender: 'male',
      })
    );

    // 删除用户
    setTimeout(() => {
      this.store.dispatch(UserActions.delUser());

      this.store.dispatch(UserActions.updateUser());
    }, 5000);
  }
}

PS:以上案例完整代码可访问 github.com/OSpoon/angu…

接入实体

实体的引入对应单个用户状态的管理来说起到的效果并不明显,所以你可以将代码回退到最初的状态,实现一个接入实体更加贴切的案例 — TodoList。

初始化项目:

创建新项目并安装依赖:

代码语言:javascript
复制
ng new angular-ngrx-todolist --standalone=false

npm install @ngrx/store @ngrx/store-devtools --save
npm install @ngrx/schematics --save-dev

# 安装接入实体的依赖
npm install @ngrx/entity --save

# 实现 uuid 生成
npm install uuid --save
npm install @types/uuid --save-dev

更新 angular.json:

代码语言:javascript
复制
{
    "cli": {
        "schematicCollections": ["@ngrx/schematics"]
    }
}

创建存储 State 的 Store:

代码语言:javascript
复制
ng generate store State --root --module=app.module.ts --state-path=store --state-interface AppState 

创建实体:

选项介绍:

选项

作用

--reducers

执行reducers存放路径,约定路径为上一级的 index.ts,也是 store 创建的文件

--skip-tests

跳过生成测试文件

示例命令:

代码语言:javascript
复制
ng generate entity store/todo/todo --reducers=../index.ts --skip-tests

PS:生成的模版代码包括了todo.actions.tstodo.model.tstodo.reducer.ts ,同时也更新了 app/store/index.ts

接入实体的代码在 todo.reducer.ts 文件中体现,下面是接入实体的核心部分,更多的适配器操作可以看文件中默认生成的模板代码:

代码语言:javascript
复制
// 1. 将 State 集成自 EntityState
export interface State extends EntityState<Todo> {
  // additional entities state properties
}

// 2. 创建后续对象操作的适配器
export const adapter: EntityAdapter<Todo> = createEntityAdapter<Todo>();

// 3. 使用创建好的适配器初始化 initialState
export const initialState: State = adapter.getInitialState({
  // additional entity state properties
});

完善 TodoList 功能:

增加 action:

代码语言:javascript
复制
add() {
  this.store.dispatch(
    TodoActions.addTodo({
      todo: {
        id: uuidv4(),
        content: this.content,
      },
    })
  );
  this.content = '';
}

删除 action:

代码语言:javascript
复制
del(todo: Todo) {
  this.store.dispatch(TodoActions.deleteTodo({ id: todo.id }));
}

清空 action:

代码语言:javascript
复制
clears() {
  this.store.dispatch(TodoActions.clearTodos());
}

使用实体提供的 Selector 获取状态:

代码语言:javascript
复制
export class AppComponent {

  todos: Observable<Todo[]>;
  total: Observable<number>;

  constructor(private store: Store) {
    this.todos = this.store.select(selectAll);
    this.total = this.store.select(selectTotal);
  }
	...
}

小结:通过接入实体,可以使用其内置的适配器对 Todo 进行添加、更新、删除、批量添加、批量更新、批量删除、清空等操作,还可以通过其内置的 Selector 方便的获取 Todos 数据,数据的长度等等信息,可以简化一大部分的开发时间。

PS:以上案例使用 Zorro 组件库,完整代码可访问 github.com/OSpoon/angu…

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-01-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 快速开始
    • 创建 Angular 项目:
      • 安装 NGRX 核心模块:
        • 创建存储 State 的 Store:
          • 创建用于添加和删除用户的 Action:
            • 创建根据 Action 来更新状态的 Reducer:
              • 创建获取状态的使用的 Selector:
                • 进入模拟场景:
                • 接入副作用
                  • 安装 effects 核心模块:
                    • 创建 User 的副作用:
                      • 编写 Test User Api:
                        • 添加新的 Actions:
                          • 完成副作用编写:
                            • 进入模拟场景:
                            • 接入实体
                              • 初始化项目:
                                • 创建实体:
                                  • 完善 TodoList 功能:
                                  相关产品与服务
                                  对象存储
                                  对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
                                  领券
                                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档