集成Redux

集成Redux

我们还是以Get操作为例,新建第一个Redux,后面章节继续扩展增加、更新、删除

1. 修改actions

第一个程序中的[3. State]中,我们生成了初始的+state,默认创建了三个Action:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// content-hrmanager.actions.ts

/**
 * 加载用户Action
 */
export const loadContentHrmanager = createAction(
  '[ContentHrmanager] Load ContentHrmanager'
);

/**
 * 加载全部用户成功Action
 */
export const loadContentHrmanagerSuccess = createAction(
  '[ContentHrmanager] Load ContentHrmanager Success',
  props<{ contentHrmanager: ContentHrmanagerEntity[] }>()
);

/**
 * 加载失败Action
 */
export const loadContentHrmanagerFailure = createAction(
  '[ContentHrmanager] Load ContentHrmanager Failure',
  props<{ error: any }>()
);

2. 修改effects

从actions流里面获取到加载action(如:loadContentHrmanager)之后,利用service和api交互。成功的场合,返回最新的数据。失败的场合,返回error信息。

我们可以利用下面两种方式,来捕获相关action。

  • 直接从action流里面获取

    • 构造方法中注入 hrManagerService
1
2
3
4
5
6
// content-hrmanager.effects.ts
// 1. 将HrManagerService引入进来
import { HrManagerService } from '@redux-app/backend';

// 2. 构造方法中注入
constructor(private actions$: Actions, private hrManagerService: HrManagerService) {}
1
- 改造loadContentHrmanager$里面run函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 改造前
this.actions$.pipe(
  ofType(ContentHrmanagerActions.loadContentHrmanager),
  fetch({
  run: (action) => {
      // Your custom service 'load' logic goes here. For now just return a success action...
      return ContentHrmanagerActions.loadContentHrmanagerSuccess({
      contentHrmanager: [],
      });
  },

  onError: (action, error) => {
      console.error('Error', error);
      return ContentHrmanagerActions.loadContentHrmanagerFailure({ error });
  },
  })


// 改造后
loadContentHrmanager$ = createEffect(() =>
  this.actions$.pipe(
    ofType(ContentHrmanagerActions.loadContentHrmanager),
    fetch({
      run: (action) => {
        return this.hrManagerService.getAll().pipe(
          // 正常,发出成功action
          map(data => (ContentHrmanagerActions.loadContentHrmanagerSuccess({contentHrmanager: data}))),
          // 异常,发出失败action
          catchError(err => of(ContentHrmanagerActions.loadContentHrmanagerFailure({ error: (err as HttpErrorResponse).message }))
          )
        );
      },

      onError: (action, error) => {
        console.error('Error', error);
        return ContentHrmanagerActions.loadContentHrmanagerFailure({ error });
      },
    })
  )
);
  • 通过DataPersistence获取action

    • 构造方法中注入 dataPersistence
1
2
3
4
5
6
7
8
// 头部引入
import { DataPersistence, fetch } from '@nrwl/angular';
import { State } from './content-hrmanager.reducer';

// 构造方法中注入
constructor(private actions$: Actions,
  private hrManagerService: HrManagerService,
  private dataPersistence: DataPersistence<State>) {}
1
- load方法改造如下
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
 * 加载数据
 */
loadContentHrmanager$ = createEffect(() =>
  this.dataPersistence.fetch(
    ContentHrmanagerActions.loadContentHrmanager, {
      run: (action) => {
        return this.hrManagerService.getAll().pipe(
          // 正常,发出成功action
          map(data => (ContentHrmanagerActions.loadContentHrmanagerSuccess({contentHrmanager: data}))),
          // 异常,发出失败action
          catchError(err => of(ContentHrmanagerActions.loadContentHrmanagerFailure({ error: (err as HttpErrorResponse).message }))
          )
        );
      },
      onError: (action, error) => {
        console.error('Error', error);
        return ContentHrmanagerActions.loadContentHrmanagerFailure({ error });
      }
    }
  )
);

3. 改造form

我们在form.component.ts中注入facade,来发射加载action来请求数据。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// form.component.ts

// 1. 定义用户集合
/**
 * 用户集合
 */
user$: Observable<ContentHrmanagerEntity[]>;

// 2. 构造方法中注入facade
constructor(private hrManagerService: HrManagerService, private hrManagerFacade: ContentHrmanagerFacade) {}

// 3. search方法改造如下:
// 发出加载全部用户数据action
this.hrManagerFacade.dispatch(loadContentHrmanager());
// 获取所有用户集合
this.user$ = this.hrManagerFacade.allContentHrmanager$;

// form.component.html
// 在"删除"按钮下面添加如下代码

<div *ngFor="let user of (user$ | async)">
  <label>{{user.id}} -- </label>
  <label>{{user.name}} -- </label>
  <label>{{user.age}}</label>
</div>

上面三步做完后,将下面的步骤执行下:

  • 安装store-devtools, 便于从chrome的redux的插件中,观察状态的变化。
1
npm i @ngrx/store-devtools --save-dev
  • app.module.ts中引入StoreDevtoolsModule
1
2
3
4
5
// 1. 头部手动导入
import { StoreDevtoolsModule } from '@ngrx/store-devtools';

// 2. imports中添加
StoreDevtoolsModule.instrument(),
  • package.json加入json-server
1
2
3
// json-server启动配置在环境中
// package.json -> scripts中加入下面这句话
"mock": "json-server --watch mock/_fixture/hr-manager.json --port 3000"

json-server配置完成后,我们可以在NPM SCRIPTS中点击server就可以运行mock数据了

4. 测试

当我们启动mock和程序后,点击查询按钮,我们会发现页面上可以看到user数据。

我们也可以在form.component.ts的构造方法中,注入action流,来监听错误的action信号/事件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// app.component.ts

// 1. 头部引入actions包
import * as ContentHrmanagerActions from '../+state/content-hrmanager.actions';

// 2. constructor中注入action$
constructor(private hrManagerFacade: ContentHrmanagerFacade, private action$: Actions) {
    // 从action$流中筛选错误的action
    this.action$.pipe(
      ofType(ContentHrmanagerActions.loadContentHrmanagerFailure)).subscribe(err => {
        console.log('err => ' + err.error);
      });
  }

当我们断开mock的话,在控制台打印错误信息。

按照ID查询数据

  • 在actions.ts中追加用户查询的单个对象的action
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// content-hrmanager.actions.ts

/**
 * 通过id查询
 */
export const loadContentHrmanagerById = createAction(
  '[ContentHrmanager] Load ContentHrmanager by id',
  props<{ id: number }>()
);

/**
 * 查询成功,返回所查用户数据
 */
export const loadContentHrmanagerByIdSuccess = createAction(
  '[ContentHrmanager] Load ContentHrmanager Success by id',
  props<{ id: number, contentHrmanager: ContentHrmanagerEntity }>()
);
  • 在reducer.ts中追加分支条件
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// content-hrmanger.reducer.ts

// 查询成功, 向store中存储最新的状态数据
on(
  ContentHrmanagerActions.loadContentHrmanagerByIdSuccess,
  (state, { id, contentHrmanager }) =>
    contentHrmanagerAdapter.setOne(contentHrmanager, {
      ...state,
      loaded: true,
      selectedId: id
    })
),
  • 在effects.ts中添加通过Id来查询用户信息
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
 * 通过id查询用户数据
 */
loadContentHrmanagerById$ = createEffect(() =>
  this.dataPersistence.fetch(
    ContentHrmanagerActions.loadContentHrmanagerById, {
      run: (action: any) => {
        // 取出参数id,查询用户数据
        return this.hrManagerService.get(action.id).pipe(
          // 正常,发出成功action
          map(data => (ContentHrmanagerActions.loadContentHrmanagerByIdSuccess({ id: action.id, contentHrmanager: data}))),
          // 异常,发出失败action
          catchError(err => of(ContentHrmanagerActions.loadContentHrmanagerFailure({ error: (err as HttpErrorResponse).message }))
          )
        );
      },
      onError: (action, error) => {
        console.error('Error', error);
        return ContentHrmanagerActions.loadContentHrmanagerFailure({ error });
      }
    }
  )
)
  • selectors.ts中利用既有的通过id取得数据的方法
1
2
3
4
5
export const getSelected = createSelector(
  getContentHrmanagerEntities,
  getSelectedId,
  (entities, selectedId) => selectedId && entities[selectedId]
);
  • facade.ts中利用既有的方法查询用户数据
1
2
3
selectedContentHrmanager$ = this.store.pipe(
  select(ContentHrmanagerSelectors.getSelected)
);
  • component中改造如下
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// form.component.ts

/**
 * 用户
 */
user$: Observable<ContentHrmanagerEntity>;

// search方法改造
const id = +this.id.nativeElement.value;
// 根据id获取用户信息
this.user$ = this.hrManagerFacade.selectedContentHrmanager$;
// 发出加载全部用户数据action
this.hrManagerFacade.dispatch(ContentHrmanagerActions.loadContentHrmanagerById({id: id}));

// form.component.html页面下方添加如下代码:
<div>
  <label>{{(user$ | async)?.id}} -- </label>
  <label>{{(user$ | async)?.name}} --</label>
  <label>{{(user$ | async)?.age}}</label>
</div>

上面做完后,我们在浏览器中测试结果如下: