创建英雄过滤¶
现在画面上已经能显示出数据了,我们更进一步,添加一些英雄的显示条件
第一步 在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 |
选择一个喜欢的主题

然后选择yes

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> |
接着运行项目
画面:

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