Strore是Angular基於Rxjs的狀態管理,保存了Redux的核心概念,並使用RxJs擴展的Redux實現。使用Observable來簡化監聽事件和訂閱等操做。
在看這篇文章以前,已經假設你已瞭解rxjs和redux。
官方文檔 有條件的話,請查看官方文檔進行學習理解。javascript
npm install @ngrx/store
下面這個Tutorial將會像你展現如何管理一個計數器的狀態和如何查詢以及將它顯示在Angular的Component上。你能夠經過StackBlitz來在線測試。css
1.建立actionshtml
src/app/counter.actions.ts import {Action} from '@ngrx/store'; export enum ActionTypes { Increment = '[Counter Component] Increment', Decrement = '[Counter Component] Decrement', Reset = '[Counter Component] Reset', } export class Increment implements Action { readonly type = ActionTyoes.Increment; } export class Decrement implements Action { readonly type = ActionTypes.Decrement; } export class Reset implements Action { readonly tyoe = Actiontypes.Reset; }
2.定義一個reducer經過所提供的action來處理計數器state的變化。java
src/app/counter.reducer.ts import {Action} from '@ngrx/store'; import {ActionTypes} from './conter.actions'; export const initailState = 0; export function conterReducer(state = initialState, action: Action) { switch(action.type) { case ActionTypes.Increment: return state + 1; case ActionTypes.Decrement: return state - 1; case ActionTypes.Reset: return 0; default: return state; } }
3.在src/app/app.module.ts
中導入 StoreModule from @ngrx/store
和 counter.reducer
git
import {StroeModule} from '@ngrx/store'; import {counterReducer} from './counter.reducer';
4.在你的AppModule
的imports
array添加StoreModule.forRoot
,並在StoreModule.forRoot
中添加count 和 countReducer對象。StoreModule.forRoot()
函數會註冊一個用於訪問store
的全局變量。github
scr/app/app.module.ts 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({ declaration: [AppComponent], imports: [ BrowserModule, StoreModule.forRoot({count: countReducer}) ], provoders: [], bootstrap: [AppComponent] }) export class AppModule {}
5.在app
文件夾下新建立一個叫my-counter
的Component,注入Store
service到你的component的constructor
函數中,並使用select
操做符在state查詢數據。更新MyCounterComponent
template,添加添加、減小和重設操做,分別調用increment,decrement,reset方法。並使用async
管道來訂閱count$
Observable。web
src/app/my-counter/my-counter.component.html <button (click)="increment()">Increment</button> <div>Current Count: {{ count$ | async }}</div> <button (click)="decrement()">Decrement</button> <button (click)="reset()">Reset Counter</button>
更新MyCounterComponent
類,建立函數並分發(dispatch)Increment,Decrement和Reset actions.typescript
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: Stare<{count: number}>) { this.count$ = store.pipe(select('count')); } increment() { this.store.dispatch(new Increment()); } decrement() { this.store.dispatch(new Decrement()); } reset() { this.store.dispatch(new Reset()); } )
6.添加MyCounter
component到AppComponent
template中npm
<app-my-counter></app-my-counter>
Actions是NgRx的核心模塊之一。Action表示在整個應用中發生的獨特的事件。從用戶與頁面的交互,與外部的網絡請求的交互和直接與設備的api交互,這些和更多的事件經過actions來描述。redux
在NgRx的許多地方都使用了actions。Actions是NgRx許多系統的輸入和輸出。Action幫助你理解如何在你的應用中處理事件。
NgRx經過簡單的interface來組成Action
:
interface Action { type: string; }
這個interface只有一個屬性:type
,string類型。這個type
屬性將描述你的應用調度的action。這個類型的值以[Source]
的形式出現和使用,用於提供它是什麼類型的操做的上下文和action在哪裏被調度(dispatched)。您能夠向actions添加屬性,以便爲操做提供其餘上下文或元數據。最多見的屬性就是payload
,它會添加action所需的全部數據。
下面列出的是做爲普通javascript對象編寫的操做的示例:
{ type: '[Auth API] Login Success' }
這個action描述了調用後端API成功認證的時間觸發。
{ type: '[Login Page]', payload: { username: string; password: string; } }
這個action描述了用戶在登陸頁面點擊登陸按鈕嘗試認證用戶的時間觸發。payload
包含了登陸頁面提供的用戶名和密碼。
有一些編寫actions的好習慣:
遵循這些指南可幫助您瞭解這些actions在整個應用程序中的流程。
下面是一個啓動登錄請求的action示例:
import {} from '@ngrx/store'; export class Login Implements Action { readonly type = '[Login Page] Login' constructor(public: payload: {username: string, password: string}){} }
action編寫成類,以便在dispatched操做時提供類型安全的方法來構造action。Login
action 實現(implements) Action
interface。在示例中,payload
是一個包含username和password的object,這是處理action所需的其餘元數據.
在dispatch時,新實例化一個實例。
login-page.component.ts click(username: string, password: string) { store.dispatch(new Login({username:username, password: password})) }
Login
action 有關於action來自於哪裏和事件發生了什麼的獨特上線文。
actions的消費者,不管是reducers(純函數)或是effects(帶反作用的函數)都使用actions的type來肯定是否要執行這個action。在feature區域,多個actions組合在一塊兒,可是每一個action都須要提供本身的type信息。看上一個Login
action 例子,你將爲action定義一些額外的信息。
import {Action} from '@ngrx/store'; export enum ActionTypes { Login = '[Login Page] Login'; } export class Login Implememts Action { readonly type = ActionTypes.Login; constructor(public paylad: {username: string, password: string}) } export type Union = Login;
將action type string放在enum中而不是直接放在class內。此外,還會使用Union
類去導出Login
class.
NgRx中的Reducers負責處理應用程序中從一個狀態到下一個狀態的轉換。Reducer函數從action的類型來肯定如何處理狀態。
Reducer函數是一個純函數,函數爲相同的輸入返回相同的輸出。它們沒有反作用,能夠同步處理每一個狀態轉化。每一個reducer都會調用最新的action,當前狀態(state)和肯定是返回最新修改的state仍是原始state。這個指南將會向你展現如何去編寫一個reducer函數,並在你的store
中註冊它,並組成獨特的state。
每個由state管理的reducer都有一些共同點:
下面這個例子是state的一組action,和相對應的reducer函數。
首先,定義一些與state交互的actions。
scoreboard-page.actions.ts import {Action} from '@ngrx/store'; export enum Actiontypes { IncrementHome = '[Scoreboard Page] Home Score', IncrementAway = '[Scoreboard Page] Away Score', Reset = '[Scoreboard Page] Score Reset', } export class IncrementHome implements Action { readonly type = ActionTypes.IncrementHome; } export class IncrementAway implements Action { readonly type = ActionTypes.IncrementAway; } export class Reset implements Action { readonly type = ActionTypes.Reset; constructor(public payload: {home: number, away: number}) {} } export type ActionsUnion = IncrementHome | IncrementAway | Reset;
接下來,建立reducer文件,導入actions,並定義這個state的特徵。
每一個reducer函數都會監聽actions,上面定義的scorebnoard actions描述了reducer處理的可能轉化。導入多組actions以處理reducer其餘的state轉化。
scoreboard.reducer.ts import * as Scoreboard from '../actions/scoreboard-page.actions'; export interface State { home: number; away: number; }
根據你捕獲的內容來定義state的特徵,它是單一的類型,如number,仍是一個含有多個屬性的object。
初始state給state提供了初始值,或是在當前state是undefined
時提供值。您可使用所需state屬性的默認值設置初始state。建立並導出變量以使用一個或多個默認值捕獲初始state。
scoreboard.reducer.ts export const initialState: Satate = { home: 0, away: 0, };
reducer函數的職責是以不可變的方式處理state的更變。定義reducer函數來處理actions來管理state。
scoreboard.reducer.ts export function reducer { satate = initialState, action: Scoreboard.ActionsUnion }: State { switch(action.type) { case Scoreboard.ActionTypes.IncrementHome: { return { ...state, home: state.home + 1, } } case Scoreboard.ActionTypes.IncrementAway: { return { ...state, away: state.away + 1, } } case Scoreboard.ActionTypes.Reset: { return action.payload; } default: { return state; } } }
Reducers將switch語句與TypeScript在您的actions中定義的區分聯合組合使用,以便在reducer中提供類型安全的操做處理。Switch語句使用type union來肯定每種狀況下正在使用的actions的正確特徵。action的types定在你的action在你的reducer函數的case語句。type union 也約束你的reducer的可用操做。
在這個例子中,reducer函數處理3個actions:IncrementHome
,IncrementAway
,Reset
。每一個action都有一個基於ActionUnion
提供的強類型。每一個action均可以不可逆的處理state。這意味着state更變不會修改源state,而是使用spread操做返回一個更變後的新的state。spread語法從當前state拷貝屬性,並建立一個新的返回。這確保每次更變都會有新的state,保證了函數的純度。這也促進了引用完整性,保證在發生狀態更改時丟棄舊引用
注意:spread操做只執行淺複製,不處理深層嵌套對象。您須要複製對象中的每一個級別以確保不變性。有些庫能夠處理深度複製,包括 lodash和 immer。
當action被調度時,全部註冊過的reducers都會接收到這個action。經過switch語句肯定是否處理這個action。由於這個緣由,每一個switch語句中老是包含default case,當這個reducer不處理action時,返回提供的state。
state在你的應用中定義爲一個large object。註冊reducer函數。註冊reducer函數來管理state的各個部分中具備關聯值的鍵。使用StoreModule.forRoot()
函數和鍵值對來定義你的state,來在你的應用中註冊一個全局的Store
。StoreModule.forRoot()
在你的應用中註冊一個全局的providers,將包含這個調度state的action和select的Store
服務注入到你的component和service中。
app.module.ts import {NgModule} from '@angular/core'; import {StoreModule} form '@ngrx/store'; import {scoreboardReducer} from './reducers/scoreboard.resucer'; @NgModule({ imports: [StoreModule.forRoot({game: scoreboardReducer})], }) export class AppModule {}
使用StoreModule.forRoot()
註冊states能夠在應用啓動時定義狀態。一般,您註冊的state始終須要當即用於應用的全部區域。
Feature states的行爲和root state相同,可是你在你的應用中須要定義具體的特徵區域。你的state是一個large object,Feature state會在這個object中以鍵值對的形式註冊。
下面這個state object的例子,你將看到Feature state如何以遞增的方式構建你的state。讓咱們從一個空的state開始。
app.module.ts @NgModule({ imports: [StoreModule.forRoot({})], }) export class AppModule {}
這裏在你的應用中建立了一個空的state
{ }
如今使用scoreboard
reducer和名稱爲ScoreboarModule
的特徵NgModule
註冊一個額外的state。
scoreboard.module.ts import { NgModule } from '@angular/core'; import { StoreModule } from '@ngrx/store'; import { scoreboardReducer } from './reducers/scoreboard.reducer'; @NgModule({ imports: [StoreModule.forFeature('game', scoreboardReducer)], }) export class ScoreboardModule {}
添加ScoreboardModule
到APPModule
。
app.module.ts import { NgModule } from '@angular/core'; import { StoreModule } from '@ngrx/store'; import { ScoreboardModule } from './scoreboard/scoreboard.module'; @NgModule({ imports: [StoreModule.forRoot({}), ScoreboardModule], }) export class AppModule {}
每一次ScoreboardModule
被加載,這個game
將會變爲這個object的一個屬性,並被管理在state中。
{ game: { home: 0, away: 0} }
Feature state的加載是eagerly仍是lazlly的,取決於你的應用。可使用Feature state 隨時間和不一樣特徵區域構建狀態對象。
Selector是一個得到store state的切片的純函數。@ngrx/store提供了一些輔助函數來簡化selection。selector提供了不少對state的切片功能。
當使用createSelector
和createFeatureSelector
函數時,@ngrx/store會跟蹤調用選擇器函數的最新參數。由於選擇器是純函數,因此當參數匹配時能夠返回最後的結果而不從新調用選擇器函數。這能夠提供性能優點,特別是對於執行昂貴計算的選擇器。這種作法稱爲memoization。
index.ts import {createSelector} from '@ngrx/store'; export interface FeatureState { counter: number; } export interface AppSatte { feature: FeatureState; } export const selectFeature = (state: AppState) => state.feature; export const selectFeatureCount = createSelector( selectFeature, (state: FeatrureState) => state.counter )
createSelector
可以從基於一樣一個state的幾個切片state中獲取一些數據。createSelector
最多可以接受8個selector函數,以得到更加完整的state selections。
在下面這個例子中,想象一下你有selectUser
object 在你的state中,你還有book object的allBooks
數組。
你想要顯示你當前用戶的全部書。
你可以使用createSelector
來實現這些。若是你在allBooks
中更新他們,你的可見的書將永遠是最新的。若是選擇了一本書,它們將始終顯示屬於您用戶的書籍,而且在沒有選擇用戶時顯示全部書籍。
結果將會是你從你的state中過濾一部分,而且他永遠是最新的。
import {createSelecotr} from '@ngrx/store'; export interface User { id: number; name: string; } export interface Book { id: number; userId: number; name: string; } export interface AppState { selectoredUser: User; allBooks: Book[]; } export const selectUser = (state: AppSate) => state.selectedUser; export const SelectAllBooks = (state: AppState) => state.allBooks; export const SelectVisibleBooks = createSelector( selectUser, selectAllBooks, (selectedUser: User, allBooks: Books[]) => { if(selectedUser && allBooks) { return allBooks.filter((book: Book) => book.UserId === selectedUser.id); }else { return allBooks; } } )
當store中沒有一個適合的select來獲取切片state,你能夠經過selector函數的props
。
在下面的例子中,咱們有計數器,並但願他乘以一個值,咱們能夠添加乘數並命名爲prop
:
index.ts export const getCount = createSelector( getCounterValue, (counter, props) => counter * props.multiply );
在這個組件內部,咱們定義了一個props
。
ngOnInit() { this.counter = this.store.pipe(select(formRoot.getCount, {multiply: 2})); }
記住,selector只將以前的輸入參數保存在了緩存中,若是你用另外一個乘數來從新使用這個selector,selector總會去從新計算它的值,這是由於他在接收兩個乘數。爲了正確地記憶selector,將selector包裝在工廠函數中以建立選擇器的不一樣實例
index.ts export const getCount = () => { createSelector( (state, props) => state.counter[props.id], (counter, props) => counter * props* multiply ); }
組件的selector如今調用工廠函數來建立不一樣的選擇器實例:
ngOnInit() { this.counter2 = this.store.pipe(select(fromRoot.getCount(), { id: 'counter2', multiply: 2 })); this.counter4 = this.store.pipe(select(fromRoot.getCount(), { id: 'counter4', multiply: 4 })); this.counter6 = this.store.pipe(select(fromRoot.getCount(), { id: 'counter6', multiply: 6 })); }
createFeatureSelector
是一個返回頂級feature state的便捷方法。它返回一個按特徵切片的state的typed selector方法。
示例
index.ts import { createSelector, createFeatureSelector } from '@ngrx/store'; export interface FeatureState { counter: number; } export interface AppState { feature: FeatureState; } export const selectFeature = createFeatureSelector<AppState, FeatureState>('feature'); export const selectFeatureCount = createSelector( selectFeature, (state: FeatureState) => state.counter );
下面這個selector沒法被編譯,由於foo
不是AppState
的一個特徵切片。
index.ts export const selectFeature = createFeatureSelector<AppSatte, FeatureState>('foo');
經過調用createSelector或createFeatureSelector返回的選擇器函數初始化具備memoized值null。在第一次將其memoized值存儲在內存中時調用selector。若是以後使用相同的參數調用selector,則它將返回memoized值。若是以後使用不一樣的參數調用選擇器,它將從新計算並更新其memoized值。
import { createSelector } from '@ngrx/store'; export interface State { counter1: number; counter2: number; } export const selectorCounter1 = (state: State) => state.counter1; export const selectorCounter2 = (state: State) => state.counter2; export const selectTotal = createSelector( selectCounter1, selectCounter2, (counter1, counter2) => counter1 + counter2 ); // selecotTotal是一個值爲null的memoized值,由於他尚未被調用。 let state = { counter1: 3, counter2: 4}; selectTotal(state); //計算值3 + 4,返回7,selectTotal的memoized值的值爲7 selectTotal(state); //不作計算,直接返回selectTotal的memoized值的值7 state = {...state, counter2: 5}; selectTotal(state); //計算值3 + 5,返回8.selectTotal的memoized值的值爲8
selector memoized值會無限期的保留在內存中。若是memoized的值是一個不須要的大數據集,它能夠從新設置memoized值爲null,這樣就能夠將大數據集從內存中刪除。這能夠經過在selector上調用release方法來完成。
selectTotal(state); // 返回memoized值8 selectTotal.release(); //memoized的值被設爲null
釋放selector也會遞歸的釋放全部的先祖選擇器。
export interface State { evenNums: number[]; oddNums: number[]; } export const selectSumEvenNums = createSelector( (state: State) => state.evenNums, evenNums => evenNums.reduce((prev, curr) => prev + curr) ); export const selectSumOddNums = createSelector( (state: State) => state.oddNums, oddNums => oddNums.reduce((prev, curr) => prev + curr) ); export const selecTotal = createSelector( selectorSumEvenNums, selectorSumOddNums, (evenSum,oddSum) => evenSum + oddSum ); selectTotal({ evenNums: [2, 4], oddNums: [1, 3], }) // selectSumEvenNums: 6 // selectSumOddNums: 4 // selectTotal: 10 selectTotal.release(); // selectSumEvenNums: null // selectSumOddNums: null // selectTotal: null
Selector可以爲你的應用state組成一個讀取模型(CQRS)。就CQRS架構模式而言,NgRx將讀取模型(Selector)與寫入模型(reducer)分開。高級技巧就是講selector與RxJs的操做符結合起來。
這個部分將涵蓋:如何將selector與pipe操做結合;演示利用createSelector
和scan
來顯示state的狀態變化歷史。
僞裝咱們有一個選擇器叫selectValue
。
咱們只使用RxJspipe操做來實現這一個功能:
app.component.ts import {map, filter } from 'rxjs/operators'; store .pipe( map(state => selectValues(state)), filter(val => val !== undefined) ) .subscribe(/* ... */)
咱們可使用select()方法重寫上述內容:
app.component.ts import { select } from '@ngrx/store'; import { map, filter } from '@rxjs/operators'; store. pipe( select(selectValues), filter(val => val !== undefined) ) .subscribe(/* ... */)
咱們使用pipe()方法來提取操做:
app.component.ts import { select } from 'ngrx/store'; import { pipe } from 'rxjs'; import { filter } from 'rxjs/operators'; export const selectFilteredValues = pipe( select(selectValues), filter(val => val !== undefined) ); store.pipe(selectFilteredValues).subscribe(/* ... */);
index.ts export const selectProjectedValues = createSelector( selectFoo, selectBar, (foo, bar) => { if(foo && bar) { return {foo, bar}; } return undefined; } );
select-last-state-transition.ts export const selectLastStateTransitions = (count: number) => { return pipe( select(selectProjectedValues), scan((acc, curr) => { return [curr, acc[0], acc[1] ].filter(val => val !== undefined); } [] as {foo: number; bar: string}[]) ); }
app.component.ts store.pipe(selectLastStateTransitons(3)).subscribe(/* ... */);
@ngrx/store
將多個reducers組合成一個reducer。
開發者能夠認爲meta-reducers像是一個在action -> reducer管道中的鉤子。Meta-reducers容許開發者在調用普通Reducer以前預處理操做。
使用metaReducers
配置meta-reducers。metaReducers會從你提供的數組中,從右到左的執行。
注意:Meta-reducers在NgRx中相似於Redux的中間件。
app.module.ts import { StoreModule, ActionReducer, MetaReducer } from '@ngrx/store'; import { reducers } from './reducers'; export function debug(reducer: ActionReducer<any>): ActionReducer<any> { return function (state, action) { console.log('state', state); console.log('action', action); return reduccer(state, action); } } export const metaReducers: MetaReducer<any>[] = [debug]; @NgModule({ imports: [StoreModule.forRoot(reducers, { metaReducers })], }) export class AppMpdule{}
使用InjectionToken和Provider經過依賴注入註冊reducer,注入root reducers到你的應用中。
app.module.ts import { NgModule, InjectionToken } from '@angular/core'; import { StoreModule, ActionReducerMap } from '@ngrx/store'; import { SomeService } from './some.service'; import * as fromRoot from './reducers'; export const REDUCER_TOKEN = new InjectionToken<ActionReducerMap<fromRoot.State>>('Registered Reducers'); export function getReducers(someService: SomeService) { return someService.getReducers(); } @NgModule({ imports: [StoreModule.forRoot(REDUCER_TOKEN)], providers: [ { provide: REDUCER_TOKEN, deps: [SomeService], useFactory: getReducers, } ], }) export class AppModule {}
經過功能模塊組成state也可以注入Reducers。
feature.module.ts import { NgModule, InjectionToken } from '@angular/core'; import { StoreModule, ActionReducerMap } from '@ngrx/store'; import * as fromFeature from './reducers'; export const FEATURE_REDUCER_TOKEN = new InjectionToken< ActionReducerMap<fromFeature.State> >('Feature Reducers'); export function getReducers(): ActionReducerMap<fromFeature.State> { return {}; } @NgModule({ imports: [StoreModule.forFeature('feature', FEATURE_REDUCER_TOKEN)], providers: [ { provide: FEATURE_REDUCER_TOKEN, useFactory: getReducers, }, ], }) export class FeatureModule {}
經過META_REDUCERS
和Provider
來注入。
app.module.ts import { MetaReducer, META_REDUCERS } from '@ngrx/store'; import { SomeService } from './some.service'; import * as fromRoot from './reducers'; export function getMetaReducers( some: SomeService ): MetaReducer<fromRoot.State>[] {} @NgModule({ providers: [ { provide: META_REDUCERS, deps: [SomeService], useFactory: getMetaReducers, }, ], }) export class AppModule {}
Entity State adapter用於管理記錄集合。
Entity提供了API去操做和查詢entity集合。
npm install @ngrx/entity --save
entity state 是具備如下接口的給定entity集合的預約義通用接口:
interface EntityState<V> { ids: string[] | number[]; entities: { [id: string | id: number]: V }; }
ids
: 集合中全部entity的id的數組集合entities
: 以id爲索引的entity合集字典
經過提供額外的屬性來擴展entity state
user.reducer.ts export interface User { id: string; name: string; } export interface State extends EntityState<User> { //額外的entity state 屬性 selectedUserId: number | null; }
爲提供的entity適配器提供一個通用的類型接口。entity適配器提供了許多的集合方法來管理entity state
export const adapter: EntityAdapter<User> = createEntityAdapter<User>();
一種爲單個entity state集合返回通用entity適配器的方法。返回的適配器提供了許多適配器方法,用於對集合類型執行操做。這個方法的構建須要提供兩個參數。
selectId
: 用於選擇字典id的方法。當entity具備id屬性時,可選。sortcomparer
: 用於集合排序的比較方法。只有在顯示以前須要對集合進行排序時才須要比較器功能。設置爲false
將會不對集合排序,這將使CRUD操做更高效。user.reducer.ts import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity'; export interface User { id: string; name: string; } export interface State extends EntityState<User> { selectedUserId: number; } export function selectUserId(a: user): string { return a.id; } export function sortByName(a: User, b: User): number { return a.name.localeCompare(b.name); } export const adapter: EntityAdapter<User> = createEntityAdapter<User>({ selectId: selectUserId, sortComparer: sortByName })
這些方法由使用createEntityAdapter時返回的適配器對象提供。這些方法在reducer函數中用於根據您提供的操做管理entity集合。
根據提供的類型返回entity state的initialState
。還經過提供的配置對象提供附加狀態。initialState提供你的reducer函數。
user.reducer.ts import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity'; export interface User { id: string; name: string; } export interface State extends EntityState<User> { // additional entities state properties selectedUserId: number | null; } export const initialState: State = adapter.getInitialState({ selectedUserId: null; }); export function reducer(state = initialState, action): State { switch(action.type) { defualt: { return state; } } }
entity適配器也提供針對entity的操做方法。這些方法能夠一次改變一個到多個記錄。若是進行了更改,則每一個方法返回新修改的state,若是未進行任何更改,則返回相同的state。
addOne
: 添加一個entity到集合中addMany
: 添加多個entity到集合中addAll
: 使用提供的集合更換當前的集合removeOne
: 從集合中刪除一個entityremoveMany
: 經過id或者謂詞從集合中刪除多個entityremoveAll
: 清空集合updateOne
: 更新集合中的一個entityupdateMany
: 更新集合中的多個entityupsertOne
: 在集合中添加或更新一個entityupsertMany
: 在集合中添加或更新多個entitymap
: 經過定義的map函數來更新集合中的多個entity,相似於Array.mapuser.models.ts export interface User { id: string; name: string; }
user.actions.ts import { Action } from '@ngrx/store'; import { Update } from '@ngrx/entity'; import { User } from '../models/user.model'; export enum UserActionTypes { LOAD_USERS = '[User]Load User', ADD_USER = '[User]Add User', UPSERT_USER = '[User]Upsert User', ADD_USERS = '[User] Add Users', UPSERT_USERS = '[User] Upsert Users', UPDATE_USER = '[User] Update User', UPDATE_USERS = '[User] Update Users', MAP_USERS = '[User] Map Users', DELETE_USER = '[User] Delete User', DELETE_USERS = '[User] Delete Users', DELETE_USERS_BY_PREDICATE = '[User] Delete Users By Predicate', CLEAR_USERS = '[User] Clear Users', } export class LoadUsers implements Action { readonly type = UserActiontypes.LOAD_USERS; constructor(public payload: { users: User[] }) {} } export class AddUser implements Action { readonly type = UserActiontypes.ADD_USER; constructor(public payload: { user: User }) {} } export class UpsertUser implements Action { readonly type = UserActiontypes.UPSERT_USER; constructor(public payload: { users: User }) {} } export class AddUsers implements Action { readonly type = UserActiontypes.ADD_USERS; constructor(public payload: { users: User[] }) {} } export class UpsertUsers implements Action { readonly type = UserActiontypes.UPSERT_USERS; constructor(public payload: { users: User[] }) {} } export class UpdateUser implements Action { readonly type = UserActiontypes.UPDATE_USER; constructor(public payload: { users: User }) {} } export class UpdateUsers implements Action { readonly type = UserActiontypes.UPDATE_USERS; constructor(public payload: { users: User[] }) {} } export class MapUsers implements Action { readonly type = UserActiontypes.MAP_USERS; constructor(public payload: { users: User[] }) {} } export class DeleteUser implements Action { readonly type = UserActiontypes.DALETE_USER; constructor(public payload: { id: string }) {} } export class DeleteUsers implements Action { readonly type = UserActiontypes.DELETE_USERS; constructor(public payload: { ids: string[] }) {} } export class DeleteUsersByPredicate implements Action { readonly type = UserActiontypes.DELETE_USERS_BY_PREDICATE; constructor(public payload: { predicate: Predicate<User> }) {} } export class ClearUsers implements Action { readonly type = UserActiontypes.CLEAR_USERS; } export type UserActionsUnion = | LoadUsers | AddUser | UpsertUser | AddUsers | UpsertUsers | UpdateUser | UpdateUsers | MapUsers | DeleteUser | DeleteUsers | DeleteUsersByPredicate | ClearUsers;
user.reducer.ts import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity'; import { User } from '../models/user.model'; import { UserActionsUnion, UserActiontypes } from '../actions/user.actions'; export interface State extends EntityState<User> { selectedUserId: number | null; } export const adapter: EntityAdapter<User> = createEntityAdapter<User>(); export conmst initialState: State = adapter.getInitialState({ selectedUserId: null; }); export function reducer(state = initialState, action: UserActionsUnion): State { switch (action.type) { case UserActionTypes.ADD_USER: { return adapter.addOne(action.payload.user, state); } case UserActionTypes.UPSERT_USER: { return adapter.upsertOne(action.payload.user, state); } case UserActionTypes.ADD_USERS: { return adapter.addMany(action.payload.users, state); } case UserActionTypes.UPSERT_USERS: { return adapter.upsertMany(action.payload.users, state); } case UserActionTypes.UPDATE_USER: { return adapter.updateOne(action.payload.user, state); } case UserActionTypes.UPDATE_USERS: { return adapter.updateMany(action.payload.users, state); } case UserActionTypes.MAP_USERS: { return adapter.map(action.payload.entityMap, state); } case UserActionTypes.DELETE_USER: { return adapter.removeOne(action.payload.id, state); } case UserActionTypes.DELETE_USERS: { return adapter.removeMany(action.payload.ids, state); } case UserActionTypes.DELETE_USERS_BY_PREDICATE: { return adapter.removeMany(action.payload.predicate, state); } case UserActionTypes.LOAD_USERS: { return adapter.addAll(action.payload.users, state); } case UserActionTypes.CLEAR_USERS: { return adapter.removeAll({ ...state, selectedUserId: null }); } default: { return state; } } } export const getSelectedUserId = (state: State) => state.selectedUser.id; const { selectIds, selectEntities, selectAll, selectTotal } = adapter.getSelectors(); export const selectUserIds = selectids; export const selectUserEntities = selectEntities; export const selectAllUsers = selectAll; export const selectUserTotal = selectTotal;
建立的entity適配器返回的getSelectors
方法提供了從entity中選擇信息的功能。
getSelectors方法將選擇器函數做爲其惟一參數,以選擇已定義entity state片斷。
index.ts import { createSelector, createFeatureSelector, ActionReducerMap, } from '@mgrx/store'; import * as fromUser from './user.reducer'; export interface State { users: fromUser.State; } export const reducers: ActionReducerMap<State> = { users: fromUser.reducer, }; export const selectUserState = createFeatureSelector<fromUser.State>('users'); export const selectUserIds = createSelector( selectUserState, fromUser.selectUserIds ); export const selectUserEntities = createSelector( selectUserState, fromUser.selectUserEntities ); export const selectAllUsers = createSelector( selectUserState, fromUser.selectAllUsers ); export const selectUserTotal = createSelector( selectUserState, fromUser.selectUserTotal ); export const selectCurrentUserId = createSelector( selectUserState, fromUser.getSelectedUserId ); export const selectCurrentUser = createSelector( selectUserEntities, selectCurrentUserId, (userEntities, userId) => userEntities[userId] );
Effects是Rxjs的反作用模型。Effects使用流來提供新的操做源來和外部交流(例如網絡請求,webScoket消息和基於時間的事件)
介紹
在基於服務的angualr應用中,component直接經過service來和外部資源進行溝通。相反的,effects提供了與服務的交互來將他們與組件隔離。effects是處理任務的地方,例如獲取數據,生成多個事件的長時間運行的任務,以及組件不須要明確瞭解這些交互的其餘外部交互。
關鍵概念
和基於組件的反作用比較
在基於服務的應用中,component經過許多不一樣的service與數據交互,這些service經過屬性和方法公開數據。這些服務可能又依賴於管理另一些數據的服務。你的component使用這些service來完成任務。
想象一下,你的應用用來管理電影。下面這個component用於獲取並展現電影列表。
movies-page.component.ts @component({ template: ` <li *ngFor="let move of movies">{{movie.name}}</li> ` }) export class MoviesPageComponent { movies: Movie[]; consatructor(private movieService: MoviesService){} ngOnInit() { this.movieService.getAll().subscribe(movies => this.movies = movies); } }
你還須要相應的service來提取電影。
@Injectable({ providenIn: 'root' }) export class MovieService { constructor (private http: HttpClient) {} getAll() { return this.http.get('/movies'); } }
這個component有多個職責:
使用Effects能夠減小component的職責。在更大的應用程序中,這變得更加劇要,由於您有多個數據源,須要多個service來獲取這些數據,而service可能依賴於其餘service。
Effects處理外部數據和交互,使service只執行與外部交互相關的任務。接下來,重構component以將電影數據放入Store中。Effects處理獲取電影數據。
@Component({ template: ` <div *ngFor="let movie of movies$ | async"> {{ movie.name }} </div> ` }) export class MoviesPageComponent { movies$: Observable = this.store.selet(state => state.movies); constructor(private store: Store<{movies: Movie[]}>) {} ngOnInit() { this.store.dispatch({type: '[movies Page]Load Movies'}) } }
電影仍然經過MoviesService獲取,但該組件再也不關注如何獲取和加載電影.它只負責聲明加載電影和使用選擇器訪問電影列表數據的意圖。Effect經過異步的方式獲取電影。
撰寫Effects
要隔離component的反作用,必須建立一個Effects類來偵聽事件和執行任務。
Effects是一個擁有多個不一樣部分的可注入服務:
Effects
裝飾器,用元數據裝飾Observable流。元數據用於註冊訂閱Store流,而後,從Effects返回的任何操做都會從新回到Store流。Effects
訂閱了Store的Observable。爲了說明如何處理上面示例中的加載電影,讓咱們看一下MovieEffects。
movie.effects.ts import { Injectable } from '@angular/core'; import { Actions, Effect, ofType } from '@ngrx/effects'; import { EMPTY } from 'rxjs'; import { map, mergeMap } from 'rxjs/operators'; @Injectable() export class MovieEffects { }