Angular 官方框架本身提供了较为全面的功能,但是却缺少诸如 Redux,Vuex 之类的状态管理框架,而 NgRx 就是弥补这个缺陷的一个第三方框架。参考了 Redux 的思想,结合了 RxJs 流的处理
1、@ngrx/store
- 基本使用
// 1、创建 Actionimport { 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');// 2、创建 Reducerimport { createReducer, on } from '@ngrx/store';import { increment, decrement, reset } from './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);}// 3、AppModule 中引入import { BrowserModule } from '@angular/platform-browser';import { NgModule } from '@angular/core';import { AppComponent } from './app.component';import { StoreModule } from '@ngrx/store';import { counterReducer } from './reducer';@NgModule({declarations: [AppComponent],imports: [BrowserModule,StoreModule.forRoot({ count: counterReducer })],providers: [],bootstrap: [AppComponent],})export class AppModule {}// 4、组件中调用import { Component } from '@angular/core';import { Store, select } from '@ngrx/store';import { Observable } from 'rxjs';import { increment, decrement, reset } from '../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 }>) { //依赖注入this.count$ = store.pipe(select('count')); // 使用 select 函数取值}increment() {this.store.dispatch(increment());}decrement() {this.store.dispatch(decrement());}reset() {this.store.dispatch(reset());}}<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>
- 进阶
1、createAction 第二个函数参数
import { createAction, props } from '@ngrx/store';export const login = createAction('[Login Page] Login',props<{ username: string; password: string }>());onSubmit(username: string, password: string) {store.dispatch(login({ username: username, password: password }));}
2、feature State
// 定义一个子状态import { createSelector, createFeatureSelector } from '@ngrx/store';export const featureKey = 'feature';export interface FeatureState {counter: number;}export interface AppState {feature: FeatureState;}export const selectFeature = createFeatureSelector<AppState, FeatureState>(featureKey);export const selectFeatureCount = createSelector(selectFeature,(state: FeatureState) => state.counter);// 2、子模块上注册子状态import { NgModule } from '@angular/core';import { StoreModule } from '@ngrx/store';import * as fromScoreboard from './reducers/scoreboard.reducer';@NgModule({imports: [StoreModule.forFeature(fromScoreboard.featureKey, fromScoreboard.reducer)],})export class ScoreboardModule {}// 2、在根模块注册import { NgModule } from '@angular/core';import { StoreModule } from '@ngrx/store';import { ScoreboardModule } from './scoreboard/scoreboard.module';@NgModule({imports: [StoreModule.forRoot({}),ScoreboardModule],})export class AppModule {}
3、createSelector 参数函数的第二个参数
export const getCount = createSelector(getCounterValue,(counter, props) => counter * props.multiply);ngOnInit() {this.counter$ = this.store.pipe(select(fromRoot.getCount, { multiply: 2 }))}
4、meta-reducer
本质上相当于 Redux 的中间价
import { StoreModule, ActionReducer, MetaReducer } from '@ngrx/store';import { reducers } from './reducers';// console.log all actionsexport function debug(reducer: ActionReducer<any>): ActionReducer<any> {return function(state, action) {console.log('state', state);console.log('action', action);return reducer(state, action);};}export const metaReducers: MetaReducer<any>[] = [debug];@NgModule({imports: [StoreModule.forRoot(reducers, { metaReducers })],})export class AppModule {}
2、@ngrx/effects
effects 用于监听 action,是一种异步 action 处理方案,类似 redux-saga
1、基础
// 1、创建 effectimport { Injectable } from '@angular/core';import { Actions, createEffect, ofType } from '@ngrx/effects';import { of } from 'rxjs';import { map, mergeMap, catchError } from 'rxjs/operators';import { MoviesService } from './movies.service';@Injectable()export class MovieEffects {loadMovies$ = createEffect(() =>this.actions$.pipe(ofType('[Movies Page] Load Movies'),mergeMap(() => this.moviesService.getAll().pipe(map(movies => ({ type: '[Movies API] Movies Loaded Success', payload: movies })),catchError(() => of({ type: '[Movies API] Movies Loaded Error' }))))));constructor(private actions$: Actions,private moviesService: MoviesService) {}}// 2、注册 effectimport { EffectsModule } from '@ngrx/effects';import { MovieEffects } from './effects/movie.effects';@NgModule({imports: [EffectsModule.forRoot([MovieEffects])],})export class AppModule {}// 或者注册 feature effectimport { EffectsModule } from '@ngrx/effects';import { MovieEffects } from './effects/movie.effects';@NgModule({imports: [EffectsModule.forFeature([MovieEffects])],})export class MovieModule {}
2、进阶
a、处理不 dispatch action 的 effect
import { Injectable } from '@angular/core';import { Actions, createEffect } from '@ngrx/effects';import { tap } from 'rxjs/operators';@Injectable()export class LogEffects {constructor(private actions$: Actions) {}logActions$ = createEffect(() =>this.actions$.pipe(tap(action => console.log(action))), { dispatch: false }); // 这里 dispatch:false 表示该action不会 dispatch}
b、effect 错误处理: 关闭 resubscriptions
import { Injectable } from '@angular/core';import { Actions, ofType, createEffect } from '@ngrx/effects';import { of } from 'rxjs';import { catchError, exhaustMap, map } from 'rxjs/operators';import {LoginPageActions,AuthApiActions,} from '../actions';import { AuthService } from '../services/auth.service';@Injectable()export class AuthEffects {logins$ = createEffect(() =>this.actions$.pipe(ofType(LoginPageActions.login),exhaustMap(action =>this.authService.login(action.credentials).pipe(map(user => AuthApiActions.loginSuccess({ user })),catchError(error => of(AuthApiActions.loginFailure({ error })))))// Errors are handled and it is safe to disable resubscription),{ useEffectsErrorHandler: false });constructor(private actions$: Actions,private authService: AuthService) {}}
c、自定义 effect 错误处理
EFFECTS_ERROR_HANDLER
3、其它
- @ngrx/store-devtools
调试工具 - @ngrx/router-store
跟 Angular 路由结合 - @ngrx/entity
方便 reducer 操作 state - @ngrx/data
将 action、reducer、entity、effect 等进行了高度组合抽象,提供了一系列更新 entity 的 API。就是说数据的更新得用它的api来,不是很自由
import { Component, OnInit } from '@angular/core';import { Observable } from 'rxjs';import { Hero } from '../../core';import { HeroService } from '../hero.service';@Component({selector: 'app-heroes',templateUrl: './heroes.component.html',styleUrls: ['./heroes.component.scss']})export class HeroesComponent implements OnInit {loading$: Observable<boolean>;heroes$: Observable<Hero[]>;constructor(private heroService: HeroService) {this.heroes$ = heroService.entities$;this.loading$ = heroService.loading$;}ngOnInit() {this.getHeroes();}add(hero: Hero) {this.heroService.add(hero);}delete(hero: Hero) {this.heroService.delete(hero.id);}getHeroes() {this.heroService.getAll();}update(hero: Hero) {this.heroService.update(hero);}}
