创建英雄过滤

现在画面上已经能显示出数据了,我们更进一步,添加一些英雄的显示条件

第一步 在reducer中定义过滤条件

修改reducer,在里面添加过滤条件的定义
hero-list-store.reducer.ts:

 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
41
42
43
44
45
46
47
48
49
50
import {
  HeroListStoreAction,
  HeroListStoreActionTypes
} from './hero-list-store.actions';
import {
  Hero
} from '@sample-app/utils';

export const HEROLISTSTORE_FEATURE_KEY = 'heroListStore';

//用来在action中定义类型
export type HeroFilterType = keyof HeroFilter;

export interface HeroFilter {
  heroId?: string;
  name?: string;
  gender?: boolean;
  age?: number;
  job?: string;
}

export interface HeroListStoreState {
  list: Hero[];     //想在画面上显示的数据的类型改为刚刚创建的Hero
  heroFilter: HeroFilter; //过滤的条件
}

export interface HeroListStorePartialState {
  readonly [HEROLISTSTORE_FEATURE_KEY]: HeroListStoreState;
}

export const initialState: HeroListStoreState = {
  list: [],
  heroFilter: {}
};

export function reducer(
  state: HeroListStoreState = initialState,
  action: HeroListStoreAction
): HeroListStoreState {
  switch (action.type) {
    case HeroListStoreActionTypes.HeroListStoreLoaded: {
      state = {
        ...state,
        list: action.payload
      };
      break;
    }
  }
  return state;
}

第二步 在selector中添加查询

由于添加了新的状态属性,所以查询中也要添加相应的值
而且我们想在画面上显示出查询过后的结果,所以要额外再加一个过滤结果的查询
hero-list-store.selectors.ts:

 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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import { createFeatureSelector, createSelector } from '@ngrx/store';
import {
  HEROLISTSTORE_FEATURE_KEY,
  HeroListStoreState,
  HeroFilter
} from './hero-list-store.reducer';
import { Hero } from '@sample-app/utils';

// Lookup the 'HeroListStore' feature state managed by NgRx
const getHeroListStoreState = createFeatureSelector<HeroListStoreState>(
  HEROLISTSTORE_FEATURE_KEY
);

const getAllHeroListStore = createSelector(
  getHeroListStoreState,
  (state: HeroListStoreState) => state.list
);

//这个属性是过滤条件
const getHeroFilter = createSelector(
  getHeroListStoreState,
  (state: HeroListStoreState) => state.heroFilter
);

//这是根据过滤条件返回符合条件的英雄
const getFiltelingHeros = createSelector(
  getAllHeroListStore,
  getHeroFilter,
  (users, filter) => users.filter(row => findHeroFilter(row, filter))
);

//过滤条件的具体操作
const filterMap = {
  heroId: (column: string, input: string) => (column || '').includes(input),
  name: (column: string, input: string) => (column || '').includes(input),
  gender: (column: boolean, input: boolean) => (input ? !column : true),
  age: (column: number, input: number) => (input <= column),
  job: (column: string, input: string) => (column || '').includes(input)
};

//过滤的方法
const findHeroFilter = (row: Hero, heroFilter: HeroFilter): boolean => {
  const filterKeys = Object.keys(heroFilter);
  if (filterKeys.length) {
    return filterKeys.reduce(
      (pre, key) => filterMap[key](row[key], heroFilter[key]) && pre,
      true
    );
  }
  return true;
};

export const heroListStoreQuery = {
  getAllHeroListStore,
  //这里也要添加进来,以供外部调用
  getHeroFilter,
  getFiltelingHeros
};

第三步 在action中添加动作

状态和查询都添加好了,接下来就是在action中添加动作了
整个过滤可以看成一个动作,所以只要添加一个action就行了
hero-list-store.actions.ts:

 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
41
42
43
44
45
46
47
48
49
50
51
import { Action } from '@ngrx/store';
import { Hero } from '@sample-app/utils';
import { HeroFilterType } from '../..';

export enum HeroListStoreActionTypes {
  LoadHeroListStore = '[HeroListStore] Load HeroListStore',
  HeroListStoreLoaded = '[HeroListStore] HeroListStore Loaded',
  HeroListStoreLoadError = '[HeroListStore] HeroListStore Load Error',
  //添加枚举定义,会在reducer处理action时用到
  SetHeroFilter = '[HeroListStore] HeroListStore set hero filter'
}

export class LoadHeroListStore implements Action {
  readonly type = HeroListStoreActionTypes.LoadHeroListStore;
}

export class HeroListStoreLoadError implements Action {
  readonly type = HeroListStoreActionTypes.HeroListStoreLoadError;
  constructor(public payload: any) {}
}

export class HeroListStoreLoaded implements Action {
  readonly type = HeroListStoreActionTypes.HeroListStoreLoaded;
  constructor(public payload: Hero[]) {}
}

//过滤数据的action
export class SetHeroFilter implements Action {
  readonly type = HeroListStoreActionTypes.SetHeroFilter;
  constructor(
    public payload: {
      prop: HeroFilterType; //用到了刚刚在reducer中定义的类型
      value: string | boolean | number; //可能出现的类型,因为Hero中只有这些类型
    }
  ) { }
}

export type HeroListStoreAction =
  | LoadHeroListStore
  | HeroListStoreLoaded
  | HeroListStoreLoadError
  //这里也要添加
  | SetHeroFilter;

export const fromHeroListStoreActions = {
  LoadHeroListStore,
  HeroListStoreLoaded,
  HeroListStoreLoadError,
  //这里也要添加
  SetHeroFilter
};

第四步 修改facade,添加属性及查询

组件只能直接访问facade,所以要将之前添加的动作在facade中加进来
hero-list-store.facade.ts:

 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
41
42
43
44
import { Injectable } from '@angular/core';

import { select, Store } from '@ngrx/store';

import { HeroListStorePartialState } from './hero-list-store.reducer';
import { heroListStoreQuery } from './hero-list-store.selectors';
import { LoadHeroListStore, SetHeroFilter } from './hero-list-store.actions';

@Injectable()
export class HeroListStoreFacade {
  allHeroListStore$ = this.store.pipe(
    select(heroListStoreQuery.getAllHeroListStore)
  );
  //在查询中添加的两个属性
  filtelingHeros$ = this.store.pipe(select(heroListStoreQuery.getFiltelingHeros));
  heroFilter$ = this.store.pipe(select(heroListStoreQuery.getHeroFilter));

  constructor(private store: Store<HeroListStorePartialState>) {}

  loadAll() {
    this.store.dispatch(new LoadHeroListStore());
  }

  //在action中添加的动作,每种属性都为其写一个方法
  setHeroFilterHeroId(value: string) {
    this.store.dispatch(new SetHeroFilter({ prop: 'heroId', value }));
  }

  setHeroFilterName(value: string) {
    this.store.dispatch(new SetHeroFilter({ prop: 'name', value }));
  }

  setHeroFilterGender(value: boolean) {
    this.store.dispatch(new SetHeroFilter({ prop: 'gender', value }));
  }

  setHeroFilterAge(value: number) {
    this.store.dispatch(new SetHeroFilter({ prop: 'age', value }));
  }

  setHeroFilterJob(value: string) {
    this.store.dispatch(new SetHeroFilter({ prop: 'job', value }));
  }
}

第五步 在reducer中添加action对应的处理

在action中添加动作后,还要再reducer中添加相应的处理
hero-list-store.reducer.ts:

 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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import {
  HeroListStoreAction,
  HeroListStoreActionTypes
} from './hero-list-store.actions';
import {
  Hero
} from '@sample-app/utils';

export const HEROLISTSTORE_FEATURE_KEY = 'heroListStore';

//用来在action中定义类型
export type HeroFilterType = keyof HeroFilter;

export interface HeroFilter {
  heroId?: string;
  name?: string;
  gender?: boolean;
  age?: number;
  job?: string;
}

export interface HeroListStoreState {
  list: Hero[];     //想在画面上显示的数据的类型改为刚刚创建的Hero
  heroFilter: HeroFilter; //过滤的条件
}

export interface HeroListStorePartialState {
  readonly [HEROLISTSTORE_FEATURE_KEY]: HeroListStoreState;
}

export const initialState: HeroListStoreState = {
  list: [],
  heroFilter: {}
};

export function reducer(
  state: HeroListStoreState = initialState,
  action: HeroListStoreAction
): HeroListStoreState {
  switch (action.type) {
    case HeroListStoreActionTypes.HeroListStoreLoaded: {
      state = {
        ...state,
        list: action.payload
      };
      break;
    }
    //action对应的处理
    case HeroListStoreActionTypes.SetHeroFilter: {
      state = {
        ...state,
        heroFilter: {
          ...state.heroFilter,
          [action.payload.prop]: action.payload.value
        }
      };
      break;
    }
  }
  return state;
}

第六步 修改画面

执行命令安装angular material

1
ng add @angular/material

选择一个喜欢的主题

安装angular marerial

然后选择yes

安装angular marerial

content-hero-lib.module.ts导入控件包:

 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
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule, Router } from '@angular/router';
import { HeroListComponent } from './hero-list/hero-list.component';
import { NgxDatatableModule } from '@swimlane/ngx-datatable';
import { StoreHeroStoreModule, HeroListStoreFacade } from '@sample-app/store/hero-store';
//用到的material控件
import { MatCardModule, MatInputModule } from '@angular/material';

@NgModule({
  imports: [
    //用到的material控件
    MatCardModule,
    MatInputModule,
    NgxDatatableModule,
    StoreHeroStoreModule,
    CommonModule,

    RouterModule.forChild([
      {path: '', pathMatch: 'full', component: HeroListComponent}
    ])
  ],
  declarations: [HeroListComponent]
})
export class ContentHeroLibModule {
  constructor(private router: Router, private authUsers: HeroListStoreFacade) {
    this.router.events
      .subscribe(comp => {
        this.authUsers.loadAll();
      });
  }
}

hero-list.component.ts:

 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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { Hero } from '@sample-app/utils';
import { HeroListStoreFacade } from '@sample-app/store/hero-store';
import { columns } from './hero-list.header';
import { map } from 'rxjs/operators';

@Component({
  selector: 'chs-hero-list-hero-list',
  templateUrl: './hero-list.component.html',
  styleUrls: ['./hero-list.component.css']
})
export class HeroListComponent implements OnInit {
  columns = columns;

  rows$: Observable<Hero[]>;

  //过滤条件
  fHeroId$: Observable<string>;
  fName$: Observable<string>;
  fGender$: Observable<boolean>;
  fAge$: Observable<number>;
  fJob$: Observable<string>;

  constructor(public authUsers: HeroListStoreFacade) {
    this.rows$ = this.authUsers.filtelingHeros$;

    this.fHeroId$ = this.authUsers.heroFilter$.pipe(map(f => f.heroId || ''));
    this.fName$ = this.authUsers.heroFilter$.pipe(map(f => f.name || ''));
    this.fGender$ = this.authUsers.heroFilter$.pipe(map(f => f.gender || true));
    this.fAge$ = this.authUsers.heroFilter$.pipe(map(f => f.age || 0));
    this.fJob$ = this.authUsers.heroFilter$.pipe(map(f => f.job || ''));
   }

  ngOnInit() {
  }

  onChangeHeroId(heroid: string) {
    this.authUsers.setHeroFilterHeroId(heroid);
  }

  onChangeName(name: string) {
    this.authUsers.setHeroFilterName(name);
  }

  onChangeGender(gender: boolean) {
    this.authUsers.setHeroFilterGender(gender);
  }

  onChangeAge(age: number){
    this.authUsers.setHeroFilterAge(age);
  }

  onChangeJob(job: string) {
    this.authUsers.setHeroFilterJob(job);
  }
}

hero-list.component.html:

 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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<mat-card>
  <mat-form-field class="input-width">
    <input
      autocomplete="on"
      #heroid
      matInput
      placeholder="英雄ID"
      [value]="fHeroId$ | async"
      (keyup.enter)="onChangeHeroId(heroid.value)"
      (blur)="onChangeHeroId(heroid.value)"
    />
  </mat-form-field>
  <mat-form-field class="input-width">
    <input
      autocomplete="on"
      #heroname
      matInput
      placeholder="英雄名"
      [value]="fName$ | async"
      (keyup.enter)="onChangeName(heroname.value)"
      (blur)="onChangeName(heroname.value)"
    />
  </mat-form-field>
  <mat-form-field class="input-width">
    <input
      autocomplete="on"
      #age
      matInput
      placeholder="年龄"
      [value]="fAge$ | async"
      (keyup.enter)="onChangeAge(age.value)"
      (blur)="onChangeAge(age.value)"
    />
  </mat-form-field>
  <mat-form-field class="input-width">
    <input
      autocomplete="on"
      #job
      matInput
      placeholder="职业"
      [value]="fJob$ | async"
      (keyup.enter)="onChangeJob(job.value)"
      (blur)="onChangeJob(job.value)"
    />
  </mat-form-field>
</mat-card>

<ngx-datatable
  #datatable
  [columns]="columns"
  [rows]="rows$ | async"
  class="material"
  [limit]="10"
  [headerHeight]="50"
  [footerHeight]="50"
  [rowHeight]="60"
  [scrollbarH]="true"
  [scrollbarV]="false"
  [selectionType]="false"
>
  <!-- Template Column -->
  <ngx-datatable-column
    *ngFor="let col of columns"
    [width]="col.width"
    [name]="col.name"
    [prop]="col.prop"
    [pipe]="col.pipe"
    [cellClass]="col.cellClass"
  >
  </ngx-datatable-column>
</ngx-datatable>

接着运行项目
画面:

画面

在英雄名中输入一个值,就会自动进行过滤:

画面