Angular8 NgRx入门指引(更于2020.11.25)

3,211 阅读7分钟

ngrx概念

  • 一个框架
  • 用于构建Angular8响应式应用
  • 用于状态管理
  • 使用可观察对象
  • 使用Typescript
  • 使用OnPush策略,变更检测更高效
  • 状态序列化存储
  • 易于测试

原理图

ngrx

  • component触发特定action(事件)
  • store中存储着state、reducer。state对象存放着感兴趣的字段
  • action可以触发effect获取业务数据,effect处理完毕,再次触发action进行通知
  • action也可以直接触发reducer更新state
  • reducer产生新的state
  • selector层层筛选出state中感兴趣的那部分字段
  • state 感兴趣的字段修改了,component对应的视图进行响应式变化
  • 综上完成解耦,一个轮回完成视图状态修改

何时使用ngrx

组件之间共享的state管理

解耦组件与service之间耦合

注意事项

  • 使用ngrx,需要考虑到对象暂存内存中,譬如离开页面,需要销毁,否则再次进入设置的是旧值;
  • angular中code执行时机的问题,先后顺序不同,出现下面可能问题:
    • 有时候code修改了,但是界面没有修改;
    • 动态设置div id="{{}}",但是使用d3.select("#{id}")始终获取不到,需要setTimeout才行;可以改用class,非动态id。

简单入门

初始化项目

ng new ngrx-demo
> ng new ngrx-demo 
? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? SCSS   [ https://sass-lang.com/documentation/syntax#scss                ]
CREATE ngrx-demo/README.md (1026 bytes)
CREATE ngrx-demo/.editorconfig (246 bytes)
CREATE ngrx-demo/.gitignore (631 bytes)
CREATE ngrx-demo/angular.json (3705 bytes)
CREATE ngrx-demo/package.json (1295 bytes)
CREATE ngrx-demo/tsconfig.json (543 bytes)
CREATE ngrx-demo/tslint.json (1953 bytes)
CREATE ngrx-demo/browserslist (429 bytes)
CREATE ngrx-demo/karma.conf.js (1021 bytes)
CREATE ngrx-demo/tsconfig.app.json (270 bytes)
CREATE ngrx-demo/tsconfig.spec.json (270 bytes)
CREATE ngrx-demo/src/favicon.ico (948 bytes)
CREATE ngrx-demo/src/index.html (294 bytes)
CREATE ngrx-demo/src/main.ts (372 bytes)
....
added 1461 packages from 1071 contributors in 85.831s
    Successfully initialized git.

创建Angular组件my-counter

ng generate @schematics/angular:component my-counter --style=scss <

安装ngrx和配套cli指令扩展

npm i @ngrx/store @ngrx/effects @ngrx/store-devtools @ngrx/schematics --save
尝试创建state,发生unhandled异常
ng generate store AppState --root --module app.module.ts

更新cli指令集
ng config cli.defaultCollection @ngrx/schematics
再次更新

reducers已生成

import {
 ActionReducer,
 ActionReducerMap,
 createFeatureSelector,
 createSelector,
 MetaReducer
} from '@ngrx/store';
import { environment } from '../../environments/environment';

export interface State {

}
export const reducers: ActionReducerMap<State> = {

};
export const metaReducers: MetaReducer<State>[] = !environment.production ? [] : [];

生成action

ng generate action actions/counter

# app/actions/counter.actions.ts
import { createAction, props } from '@ngrx/store';

export const loadCounters = createAction(
  '[Counter] Load Counters'
);

export const loadCountersSuccess = createAction(
  '[Counter] Load Counters Success',
  props<{ data: any }>()
);

export const loadCountersFailure = createAction(
  '[Counter] Load Counters Failure',
  props<{ error: any }>()
);
  • code修改如下

import { createAction } from '@ngrx/store';

export const increment = createAction('[Counter Component] Increment');
export const decrement = createAction('[Counter Component] Decrement');
export const reset = createAction('[Counter Component] Reset');

定义reducer初始状态为数值

# app/counter.reducer.ts
import { createReducer, on } from '@ngrx/store';
import { increment, decrement, reset } from './counter.actions';

export const initialState = 0;

const _counterReducer = createReducer(initialState,
  on(increment, state => state + 1),
  on(decrement, state => state - 1),
  on(reset, state => 0),
);

export function counterReducer(state, action) {
  return _counterReducer(state, action);
}

注册state

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';

import { StoreModule } from '@ngrx/store';
import { counterReducer } from './counter.reducer';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    StoreModule.forRoot({ count: counterReducer })     // 注册时指定key
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

定义组件

# app / my-counter / my-counter.component.html
<button id="increment" (click)="increment()">Increment</button>
<div>Current Count: {{ count$ | async }}</div> 
<button id="decrement" (click)="decrement()">Decrement</button>
<button id="reset" (click)="reset()">Reset Counter</button>

组件触发action

# app / my-counter / my-counter.component.ts
import { Component } from '@angular/core';
import { Store, select } from '@ngrx/store';
import { Observable } from 'rxjs';
import { increment, decrement, reset } from '../counter.actions';

@Component({
  selector: 'app-my-counter',
  templateUrl: './my-counter.component.html',
  styleUrls: ['./my-counter.component.css'],
})
export class MyCounterComponent {
  count$: Observable<number>;

  constructor(private store: Store<{ count: number }>) { //这里需要key-count保持一致,
    this.count$ = store.pipe(select('count')); //这里关键点,在于app.module.ts注册时候的key
  }

  increment() {
    console.log('counter increment...');
    this.store.dispatch(increment());
  }

  decrement() {
    this.store.dispatch(decrement());
  }

  reset() {
    this.store.dispatch(reset());
  }
}

组件生效

# app.component.html 
<div>
  this is app component 
  <app-my-counter></app-my-counter>
</div>
# 界面按钮点击,观测数据变化

store.pipe写法解惑

# 查看store源码,我们得到Observable 
export declare class Store<T> extends Observable<T> implements Observer<Action> {
  ....
}

store.select也是Observable

export declare function select<T, Props, K>(mapFn: (state: T, props: Props) => K, props?: Props): (source$: Observable<T>) => Observable<K>;
this.store
  .select(fromStore.getProductsState)
  .map(state => state.pizzas)
  .map(pizzas => pizza.entities);

进阶

当reducer初始状态state为对象时

# counter.reducer.ts
import { createReducer, on } from '@ngrx/store';
import { increment, decrement, reset } from './counter.actions';

export interface State {
  away: number;
}

export const initialState: State = {  
  away: 0,
};

const _counterReducer = createReducer(initialState,
  on(increment, (state) => {
     console.log('reducer increment....');
     return {...state, away: state.away + 1};
  }),
  on(decrement, state => ({...state, away: state.away - 1})),
  on(reset, state => ({...state, away: 0})),
);

export function counterReducer(state, action) {
  return _counterReducer(state, action);
}

延迟加载state注册

# 新增文件app/feature.module.ts
import { NgModule } from '@angular/core';
import { StoreModule } from '@ngrx/store';
import { counterReducer } from './counter.reducer';

@NgModule({
  imports: [
    StoreModule.forFeature('countFeture', counterReducer) # 需要注意,对应的调整
  ],
})
export class ScoreboardModule {}
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { StoreModule } from '@ngrx/store';
import { counterReducer } from './counter.reducer';
import { MyCounterComponent } from './my-counter/my-counter.component';
import { ScoreboardModule } from './feature.module';

@NgModule({
  declarations: [
    AppComponent,
    MyCounterComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    StoreModule.forRoot({}),
    ScoreboardModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

root中配置前置reducer

# pre.reducer.ts
import { ActionReducer, MetaReducer } from '@ngrx/store';

export function debug(reducer: ActionReducer<any>): ActionReducer<any> {
    return function( state, action) {
      console.log('pre state', state);
      console.log('pre action', action);
      return reducer(state, action);
    };
}
export const metaReducers: MetaReducer<any>[] = [debug];
# root中配置
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { StoreModule } from '@ngrx/store';
import { counterReducer } from './counter.reducer';
import { MyCounterComponent } from './my-counter/my-counter.component';
import { ScoreboardModule } from './feature.module';
import { metaReducers } from './pre.reducer';

@NgModule({
  declarations: [
    AppComponent,
    MyCounterComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    StoreModule.forRoot({}, { metaReducers }), //前置reducer
    ScoreboardModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

# 输出日志
counter increment...
pre state {countFeture: {…}}
pre action {type: "[Counter Component] Increment"}
reducer increment....

feature中配置前置reducer

import { NgModule } from '@angular/core';
import { StoreModule } from '@ngrx/store';
import { counterReducer } from './counter.reducer';
import { metaReducers } from './pre.reducer';

@NgModule({
  imports: [
    StoreModule.forFeature('countFeture', counterReducer , { metaReducers })
  ],
})
export class ScoreboardModule {}
# 输出日志
counter increment...
pre state {countFeture: {…}}
pre action {type: "[Counter Component] Increment"}
reducer increment....

selector解读

Selector 源码 -- state相关的函数
# Selector 产生V类型结果
export declare type Selector<T, V> = (state: T) => V; 

SelectorWithProps源码

# SelectorWithProps 
export declare type SelectorWithProps<State, Props, Result> = (state: State, props: Props) => Result;

MemoizedSelector是Selector子集

export interface MemoizedSelector<State, Result, ProjectorFn = DefaultProjectorFn<Result>> extends Selector<State, Result> {
    release(): void;
    projector: ProjectorFn;
    setResult: (result?: Result) => void;
}
export interface MemoizedSelectorWithProps<State, Props, Result, ProjectorFn = DefaultProjectorFn<Result>> extends SelectorWithProps<State, Props, Result> {
    release(): void;
    projector: ProjectorFn;
    setResult: (result?: Result) => void;
}

createFeatureSelector源码

# 不可以传递props参数
export declare function createFeatureSelector<T>(featureName: string): MemoizedSelector<object, T>;
export declare function createFeatureSelector<T, V>(featureName: keyof T): MemoizedSelector<T, V>;

createSelector源码

export declare function createSelector<State, S1, Result>(s1: Selector<State, S1>, projector: (s1: S1) => Result): MemoizedSelector<State, Result>;
export declare function createSelector<State, Props, S1, Result>(s1: SelectorWithProps<State, Props, S1>, projector: (s1: S1, props: Props) => Result): MemoizedSelectorWithProps<State, Props, Result>;
export declare function createSelector<State, S1, Result>(selectors: [Selector<State, S1>], projector: (s1: S1) => Result): MemoizedSelector<State, Result>;
export declare function createSelector<State, Props, S1, Result>(selectors: [SelectorWithProps<State, Props, S1>], projector: (s1: S1, props: Props) => Result): MemoizedSelectorWithProps<State, Props, Result>;
export declare function createSelector<State, S1, S2, Result>(s1: Selector<State, S1>, s2: Selector<State, S2>, projector: (s1: S1, s2: S2) => Result): MemoizedSelector<State, Result>;
export declare function createSelector<State, Props, S1, S2, Result>(s1: SelectorWithProps<State, Props, S1>, s2: SelectorWithProps<State, Props, S2>, projector: (s1: S1, s2: S2, props: Props) => Result): MemoizedSelectorWithProps<State, Props, Result>;
export declare function createSelector<State, S1, S2, Result>(selectors: [Selector<State, S1>, Selector<State, S2>], projector: (s1: S1, s2: S2) => Result): MemoizedSelector<State, Result>;
export declare function createSelector<State, Props, S1, S2, Result>(selectors: [SelectorWithProps<State, Props, S1>, SelectorWithProps<State, Props, S2>], projector: (s1: S1, s2: S2, props: Props) => Result): MemoizedSelectorWithProps<State, Props, Result>;
export declare function createSelector<State, S1, S2, S3, Result>(s1: Selector<State, S1>, s2: Selector<State, S2>, s3: Selector<State, S3>, projector: (s1: S1, s2: S2, s3: S3) => Result): MemoizedSelector<State, Result>;
export declare function createSelector<State, Props, S1, S2, S3, Result>(s1: SelectorWithProps<State, Props, S1>, s2: SelectorWithProps<State, Props, S2>, s3: SelectorWithProps<State, Props, S3>, projector: (s1: S1, s2: S2, s3: S3, props: Props) => Result): MemoizedSelectorWithProps<State, Props, Result>;
export declare function createSelector<State, S1, S2, S3, Result>(selectors: [Selector<State, S1>, Selector<State, S2>, Selector<State, S3>], projector: (s1: S1, s2: S2, s3: S3) => Result): MemoizedSelector<State, Result>;
export declare function createSelector<State, Props, S1, S2, S3, Result>(selectors: [SelectorWithProps<State, Props, S1>, SelectorWithProps<State, Props, S2>, SelectorWithProps<State, Props, S3>], projector: (s1: S1, s2: S2, s3: S3, props: Props) => Result): MemoizedSelectorWithProps<State, Props, Result>;
export declare function createSelector<State, S1, S2, S3, S4, Result>(s1: Selector<State, S1>, s2: Selector<State, S2>, s3: Selector<State, S3>, s4: Selector<State, S4>, projector: (s1: S1, s2: S2, s3: S3, s4: S4) => Result): MemoizedSelector<State, Result>;
export declare function createSelector<State, Props, S1, S2, S3, S4, Result>(s1: SelectorWithProps<State, Props, S1>, s2: SelectorWithProps<State, Props, S2>, s3: SelectorWithProps<State, Props, S3>, s4: SelectorWithProps<State, Props, S4>, projector: (s1: S1, s2: S2, s3: S3, s4: S4, props: Props) => Result): MemoizedSelectorWithProps<State, Props, Result>;
export declare function createSelector<State, S1, S2, S3, S4, Result>(selectors: [Selector<State, S1>, Selector<State, S2>, Selector<State, S3>, Selector<State, S4>], projector: (s1: S1, s2: S2, s3: S3, s4: S4) => Result): MemoizedSelector<State, Result>;
export declare function createSelector<State, Props, S1, S2, S3, S4, Result>(selectors: [SelectorWithProps<State, Props, S1>, SelectorWithProps<State, Props, S2>, SelectorWithProps<State, Props, S3>, SelectorWithProps<State, Props, S4>], projector: (s1: S1, s2: S2, s3: S3, s4: S4, props: Props) => Result): MemoizedSelectorWithProps<State, Props, Result>;
export declare function createSelector<State, S1, S2, S3, S4, S5, Result>(s1: Selector<State, S1>, s2: Selector<State, S2>, s3: Selector<State, S3>, s4: Selector<State, S4>, s5: Selector<State, S5>, projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5) => Result): MemoizedSelector<State, Result>;
export declare function createSelector<State, Props, S1, S2, S3, S4, S5, Result>(s1: SelectorWithProps<State, Props, S1>, s2: SelectorWithProps<State, Props, S2>, s3: SelectorWithProps<State, Props, S3>, s4: SelectorWithProps<State, Props, S4>, s5: SelectorWithProps<State, Props, S5>, projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5, props: Props) => Result): MemoizedSelectorWithProps<State, Props, Result>;
export declare function createSelector<State, S1, S2, S3, S4, S5, Result>(selectors: [Selector<State, S1>, Selector<State, S2>, Selector<State, S3>, Selector<State, S4>, Selector<State, S5>], projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5) => Result): MemoizedSelector<State, Result>;
export declare function createSelector<State, Props, S1, S2, S3, S4, S5, Result>(selectors: [SelectorWithProps<State, Props, S1>, SelectorWithProps<State, Props, S2>, SelectorWithProps<State, Props, S3>, SelectorWithProps<State, Props, S4>, SelectorWithProps<State, Props, S5>], projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5, props: Props) => Result): MemoizedSelectorWithProps<State, Props, Result>;
export declare function createSelector<State, S1, S2, S3, S4, S5, S6, Result>(s1: Selector<State, S1>, s2: Selector<State, S2>, s3: Selector<State, S3>, s4: Selector<State, S4>, s5: Selector<State, S5>, s6: Selector<State, S6>, projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5, s6: S6) => Result): MemoizedSelector<State, Result>;
export declare function createSelector<State, Props, S1, S2, S3, S4, S5, S6, Result>(s1: SelectorWithProps<State, Props, S1>, s2: SelectorWithProps<State, Props, S2>, s3: SelectorWithProps<State, Props, S3>, s4: SelectorWithProps<State, Props, S4>, s5: SelectorWithProps<State, Props, S5>, s6: SelectorWithProps<State, Props, S6>, projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5, s6: S6, props: Props) => Result): MemoizedSelectorWithProps<State, Props, Result>;
export declare function createSelector<State, S1, S2, S3, S4, S5, S6, Result>(selectors: [Selector<State, S1>, Selector<State, S2>, Selector<State, S3>, Selector<State, S4>, Selector<State, S5>, Selector<State, S6>], projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5, s6: S6) => Result): MemoizedSelector<State, Result>;
export declare function createSelector<State, Props, S1, S2, S3, S4, S5, S6, Result>(selectors: [SelectorWithProps<State, Props, S1>, SelectorWithProps<State, Props, S2>, SelectorWithProps<State, Props, S3>, SelectorWithProps<State, Props, S4>, SelectorWithProps<State, Props, S5>, SelectorWithProps<State, Props, S6>], projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5, s6: S6, props: Props) => Result): MemoizedSelectorWithProps<State, Props, Result>;
export declare function createSelector<State, S1, S2, S3, S4, S5, S6, S7, Result>(s1: Selector<State, S1>, s2: Selector<State, S2>, s3: Selector<State, S3>, s4: Selector<State, S4>, s5: Selector<State, S5>, s6: Selector<State, S6>, s7: Selector<State, S7>, projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5, s6: S6, s7: S7) => Result): MemoizedSelector<State, Result>;
export declare function createSelector<State, Props, S1, S2, S3, S4, S5, S6, S7, Result>(s1: SelectorWithProps<State, Props, S1>, s2: SelectorWithProps<State, Props, S2>, s3: SelectorWithProps<State, Props, S3>, s4: SelectorWithProps<State, Props, S4>, s5: SelectorWithProps<State, Props, S5>, s6: SelectorWithProps<State, Props, S6>, s7: SelectorWithProps<State, Props, S7>, projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5, s6: S6, s7: S7, props: Props) => Result): MemoizedSelectorWithProps<State, Props, Result>;
export declare function createSelector<State, S1, S2, S3, S4, S5, S6, S7, Result>(selectors: [Selector<State, S1>, Selector<State, S2>, Selector<State, S3>, Selector<State, S4>, Selector<State, S5>, Selector<State, S6>, Selector<State, S7>], projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5, s6: S6, s7: S7) => Result): MemoizedSelector<State, Result>;
export declare function createSelector<State, Props, S1, S2, S3, S4, S5, S6, S7, Result>(selectors: [SelectorWithProps<State, Props, S1>, SelectorWithProps<State, Props, S2>, SelectorWithProps<State, Props, S3>, SelectorWithProps<State, Props, S4>, SelectorWithProps<State, Props, S5>, SelectorWithProps<State, Props, S6>, SelectorWithProps<State, Props, S7>], projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5, s6: S6, s7: S7, props: Props) => Result): MemoizedSelectorWithProps<State, Props, Result>;
export declare function createSelector<State, S1, S2, S3, S4, S5, S6, S7, S8, Result>(s1: Selector<State, S1>, s2: Selector<State, S2>, s3: Selector<State, S3>, s4: Selector<State, S4>, s5: Selector<State, S5>, s6: Selector<State, S6>, s7: Selector<State, S7>, s8: Selector<State, S8>, projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5, s6: S6, s7: S7, s8: S8) => Result): MemoizedSelector<State, Result>;
export declare function createSelector<State, Props, S1, S2, S3, S4, S5, S6, S7, S8, Result>(s1: SelectorWithProps<State, Props, S1>, s2: SelectorWithProps<State, Props, S2>, s3: SelectorWithProps<State, Props, S3>, s4: SelectorWithProps<State, Props, S4>, s5: SelectorWithProps<State, Props, S5>, s6: SelectorWithProps<State, Props, S6>, s7: SelectorWithProps<State, Props, S7>, s8: SelectorWithProps<State, Props, S8>, projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5, s6: S6, s7: S7, s8: S8, props: Props) => Result): MemoizedSelectorWithProps<State, Props, Result>;
export declare function createSelector<State, S1, S2, S3, S4, S5, S6, S7, S8, Result>(selectors: [Selector<State, S1>, Selector<State, S2>, Selector<State, S3>, Selector<State, S4>, Selector<State, S5>, Selector<State, S6>, Selector<State, S7>, Selector<State, S8>], projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5, s6: S6, s7: S7, s8: S8) => Result): MemoizedSelector<State, Result>;
export declare function createSelector<State, Props, S1, S2, S3, S4, S5, S6, S7, S8, Result>(selectors: [SelectorWithProps<State, Props, S1>, SelectorWithProps<State, Props, S2>, SelectorWithProps<State, Props, S3>, SelectorWithProps<State, Props, S4>, SelectorWithProps<State, Props, S5>, SelectorWithProps<State, Props, S6>, SelectorWithProps<State, Props, S7>, SelectorWithProps<State, Props, S8>], projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5, s6: S6, s7: S7, s8: S8, props: Props) => Result): MemoizedSelectorWithProps<State, Props, Result>;
e

select源码

export declare function select<T, Props, K>(mapFn: (state: T, props: Props) => K, props?: Props): (source$: Observable<T>) => Observable<K>;
//其中mapFn: (state: T, props: Props) => K 就是SelectorWithProps

总结

  • createSelector(): 入参state函数,返回selector纯函数;形式为state函数;可以嵌套
  • createFeatureSelector:入参state函数,返回selector纯函数;形式为state函数;可以嵌套
  • selector():入参state函数,也可入参selector纯函数;返回Observable可观察对象

selector实践

定义state对象

# counter.selector.ts
export interface State {
    count: number;
}
export interface AppState {
   feature: State;
}

feature中注册

@NgModule({
  imports: [
    StoreModule.forFeature('feature', counterReducer )
  ],
})
export class ScoreboardModule {}

定义关心的state字段函数

export const countFeatureFn = (state: AppState, props) => {
    console.log('feature....', state);
    return state.feature;
};

export const countStateFn = (state: State, props) => {
    console.log('state....', state);
    return  state.count * props.multiply;
};

创建selector函数

export const countStateSelector = createSelector(countFeatureFn, countStateFn);

通过select(),建立与store的关联,订阅数据

@Component({
  selector: 'app-my-counter',
  templateUrl: './my-counter.component.html',
  styleUrls: ['./my-counter.component.scss'],
})
export class MyCounterComponent {
  count$: Observable<number>;

  constructor(private store: Store<{feature: State }>) {
    this.count$ = store.pipe(select(countStateSelector, { multiply: 3}));
  }

  ....
}

触发store中state改变,刷新component即可

increment() {
    // console.log('counter increment...');
    this.store.dispatch(increment());
  }

  decrement() {
    this.store.dispatch(decrement());
  }

  reset() {
    this.store.dispatch(reset());
  }

引入常量,优化

# feature.enum.ts
export const featureKey = 'feature';

使用createFeatureSelector

import { createSelector, createFeatureSelector } from '@ngrx/store';
import { featureKey } from './feature.enum';

export interface State {
    count: number;
}
export interface AppState {
   [featureKey]: State;
}

// export const countFeatureFn = (state: AppState, props) => {
//     console.log('feature....', state);
//     return state[featureKey];
// };

export const countStateFn = (state: State, props) => {
    console.log('state....', state);
    return  state.count * props.multiply;
};

export const selectFeature = createFeatureSelector<AppState, State>(featureKey);

export const countStateSelector = createSelector(selectFeature, countStateFn);

effects 实践

effects用途

  • 监听特定动作,调用业务逻辑。譬如登录逻辑。
  • 特定逻辑调用完毕后,发射特定的动作,触发reducer,修改状态,更新组件。

生成effects

  • cli指令
ng generate effect effects/hello --root --module app.module.ts

  • 初步code如下
import { Injectable } from '@angular/core';
import { Actions, createEffect } from '@ngrx/effects';

@Injectable()
export class HelloEffects {
  constructor(private actions$: Actions) {}
}

  • 修改如下:
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { EMPTY } from 'rxjs';
import { map, mergeMap, catchError } from 'rxjs/operators';
import { HelloService } from './hello.service';
import { increment, decrement, reset, hello, helloSuccess } from './counter.actions';

@Injectable()
export class HelloEffects {

  loadMovies$ = createEffect(() => this.actions$.pipe(
    ofType(hello),
    mergeMap((action) => this.helloService.getHello(action)
      .pipe(
        map((movies: string) => {
          console.log('in effect map...', movies);
          return helloSuccess({title: movies});
        }),
        catchError(() => EMPTY)
      )
      )
    )
  );

  constructor(
    private actions$: Actions,
    private helloService: HelloService
  ) {}
}

root中注册effects

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { StoreModule } from '@ngrx/store';
import { counterReducer } from './counter.reducer';
import { MyCounterComponent } from './my-counter/my-counter.component';
import { ScoreboardModule } from './feature.module';
import { metaReducers } from './pre.reducer';
import { EffectsModule } from '@ngrx/effects';
import { HelloEffects } from './hello.effects';

@NgModule({
  declarations: [
    AppComponent,
    MyCounterComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    StoreModule.forRoot({}),
    EffectsModule.forRoot([HelloEffects]),
    ScoreboardModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

添加service

import { Injectable } from '@angular/core';
import { of } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class HelloService {

  constructor() { }

  getHello() {
    // console.log('service get hello....');
    return  of('hello world ngrx');
  }
}

action修改

import { createAction, props } from '@ngrx/store';
import { State, TitleState } from './counter.selector';

export const increment = createAction('[Counter Component] Increment');
export const decrement = createAction('[Counter Component] Decrement');
export const reset = createAction('[Counter Component] Reset');

# action中可以定义传参
export const hello = createAction('[Hello Service] call', props<TitleState>());
export const helloSuccess = createAction('[Hello Service] call success', props<TitleState>()); 

组件中触发effect定义的动作

import { Component, OnInit } from '@angular/core';
import { Store, select, createSelector, createFeatureSelector } from '@ngrx/store';
import { Observable } from 'rxjs';
import { increment, decrement, reset } from '../counter.actions';
import { countStateSelector, titleStateSelector, State, AppState } from '../counter.selector';
import { HelloService } from '../hello.service';
import { hello } from '../counter.actions';

@Component({
  selector: 'app-my-counter',
  templateUrl: './my-counter.component.html',
  styleUrls: ['./my-counter.component.scss'],
})
export class MyCounterComponent implements OnInit{

  count$: Observable<number>;
  title$: Observable<string>;

  constructor(private store: Store<AppState>) {
    this.count$ = store.pipe(select(countStateSelector, { multiply: 2}));
    this.title$ = store.pipe(select(titleStateSelector));
  }

  ngOnInit(): void {
    this.store.dispatch(hello({title: 'hello dispatch', name: 'ngrx '}));
  }

  increment() {
    // console.log('counter increment...');
    this.store.dispatch(increment());
  }

  decrement() {
    this.store.dispatch(decrement());
  }

  reset() {
    this.store.dispatch(reset());
  }
}

selectors筛选特定数据

import { createSelector, createFeatureSelector } from '@ngrx/store';
import { featureKey } from './feature.enum';

export interface AppState {
    [featureKey]: State;
 }
export interface State {
    count: number;
}
export interface TitleState {
    title: string;
    name?: string;
}

export const countStateFn = (state: State, props) => {
    console.log('count state....', state);
    return  state.count * props.multiply;
};

export const titleStateFn = (state: TitleState) => {
    console.log('title state....', state);
    return state.title;
};

export const countFeature = createFeatureSelector<AppState, State>(featureKey);
export const titleFeature = createFeatureSelector<AppState, TitleState>(featureKey);


// logic selector
export const countStateSelector = createSelector(countFeature, countStateFn);
export const titleStateSelector = createSelector(titleFeature, titleStateFn);

reducer进行数据处理

import { createReducer, on } from '@ngrx/store';
import { increment, decrement, reset, helloSuccess } from './counter.actions';
import { State, TitleState } from './counter.selector';

export const initialState: State & TitleState = {
  count: 0,
  title: '',
};
const localCounterReducer = createReducer(initialState,
  on(increment, (state) => {
    console.log('incre....', state);
    const res = {...state, count: state.count + 1};
    return res;
  }),
  on(decrement, state => ({...state, count: state.count - 1})),
  on(reset, state => ({...state, count: 0})),
  on(helloSuccess, (state, { title } ) => {
     console.log('param title ....', title);
     return { ...state, title } ;
  })
);
export function counterReducer(state, action) {
  // console.log('counter reducer...', state, action);
  return localCounterReducer(state, action);
};

更多推荐

Angular8 HttpClient 30分钟深入了解下

Angular8 日常开发填坑指南

参考文献:

ngrx