Angular 官方框架本身提供了较为全面的功能,但是却缺少诸如 Redux,Vuex 之类的状态管理框架,而 NgRx 就是弥补这个缺陷的一个第三方框架。参考了 Redux 的思想,结合了 RxJs 流的处理
1、@ngrx/store
- 基本使用
// 1、创建 Action
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');
// 2、创建 Reducer
import { 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 actions
export 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、创建 effect
import { 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、注册 effect
import { EffectsModule } from '@ngrx/effects';
import { MovieEffects } from './effects/movie.effects';
@NgModule({
imports: [
EffectsModule.forRoot([MovieEffects])
],
})
export class AppModule {}
// 或者注册 feature effect
import { 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);
}
}
网友评论