今天目標:完成會員部分的 ngrx/store
複習一下我們的目標
複習一下 [ngrx/store-7] 純函數 (Pure Function), Effects 在 store 中的角色就是要處理有“副作用”的部分,例如跟後端聯繫,讀出寫入 Localstorage 等等,不是純函數的部分,在會員篇的狀態中, Login, Logout, GetUser 都會有“副作用”,所以也都會用到 Effects,
第一步:建立 src/app/store/effects/user.effects.ts
ng generate class user.effects --spec
第二步:修改 user.effects.ts
import { Injectable } from '@angular/core';
import { Action } from '@ngrx/store';
import { Effect, Actions } from '@ngrx/effects';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { map, switchMap, filter, catchError } from 'rxjs/operators';
import { UserService } from '../../user/service/user.service';
import { UtilsService } from '../../services/utils.service';
import { User, Response } from '../../models';
import * as actions from '../actions';
@Injectable()
export class UserEffects {
constructor(
private action$: Actions,
private userService: UserService,
private utils: UtilsService
) { }
@Effect()
loginEffect$: Observable<Action> = this.action$.ofType(actions.LOGIN).pipe(
map((action: actions.LoginAction) => action.payload),
switchMap((user: User) => {
return this.userService.loginServer(user).pipe(
map((res: Response) => {
if (res.success) {
if (user.rememberMe) {
this.utils.writeToken(res.payload);
}
return new actions.LoginSuccessAction(user.username);
} else {
return new actions.LoginFailAction(res.payload);
}
}),
catchError((err) => of(new actions.LoginFailAction(err))),
)
}),
);
@Effect()
logoutEffect$: Observable<Action> = this.action$.ofType(actions.LOGOUT).pipe(
map(() => {
this.utils.removeToken();
return (new actions.LogoutSuccessAction());
})
)
@Effect()
getUserEffect$: Observable<Action> = this.action$.ofType(actions.GETUSER).pipe(
switchMap(() => {
return this.userService.getUserFromServer().pipe(
filter(user => (user !== null)),
map((user: User) => new actions.GetUserSuccessAction(user.username)),
catchError(err => of(new actions.GetUserFailAction(err))),
)
})
)
}
constructor()
導入動作 Actions(監聽用),使用者服務 UserService(跟後端連結)跟工具服務 UtilsService (寫入讀出 Localstorage)this.action$.ofType()
“監聽“ 動作 (Action),處理完“副作用“的部分後,最後會輸出 Observable<Action>
另外的動作loginEffect$
監聽 "LOGIN" 的動作,一聽到這個動作,先從動作的payload
取出資料,之前我們在定義export class LoginAction implements Action {
readonly type = LOGIN;
constructor(public payload: User) { }
}
時,已經定義了 payload
是 User
型態,接著用 switchMap
來使用 UserService,請回憶一下[ngrx/store -5] 高階 (High Order) Observable,因為 UserService.loginServer()
本身也是 Observable,我們需要用到高階運算子,如果登入成功的話,寫入從後端傳回的 token,分派一個成功的動作LoginSuccessAction
交給 Reducer,前面定義過的 Reducer,就會產生新的狀態
//...省略
switch (action.type) {
case actions.LOGOUT:
return initialState;
case actions.LOGIN_SUCCESS:
case actions.GETUSER_SUCCESS:
return { ...state, currentUser: action.payload, isLogin: true };
default:
return state
}
//...
logoutEffect$
監聽 "LOGOUT" 的動作,簡單的做清除 tokengetUserEffect$
監聽 "GETUSER" 的動作,一樣使用使用者服務,從後端取回使用者名稱,在依後端回覆的狀況,產生GetUserSuccessAction
或是 GetUserFailAction
給 Reducer第三步:修改 src/app/store/effects/index.ts
import { UserEffects } from './user.effects';
export const effects: any[] = [UserEffects];
export * from './user.effects';
放在 effects 的陣列中,這樣 app.module.ts 就會註冊這個 effects
有人形容 store 就像前端的資料庫,如果狀態樹是表格 (Table) 的話,那選擇器(Selector) 就像是事先寫好的查詢(Query)一樣,複雜的 Selector 可能會跨不同的狀態樹枝,就像資料庫的 Join 般連結起使用者想要得到的資料,我們以後會看到
第一步: 建立 src/app/store/selectors/selectors.ts
ng generate class selectors --spec
第二步:修改 selectors.ts
import { createSelector } from 'reselect';
import * as fromReducer from '../reducers';
import * as user from '../reducers/user.reducers';
// for selector
export const getUserState = (state: fromReducer.State) => state.user; // point to users state subtree
export const getIsLogin = createSelector(getUserState, user.getIsLogin);
export const getCurrentUser = createSelector(getUserState, user.getCurrentUser);
導入 createSelector
函數,詳情請參考官方文件,前面的參數指向狀態樹的哪一個部分,這個例子 getUserState
指向狀態樹的 state.user
樹枝(在 store/reducer/index.ts 中),後面的參數傳回從這個樹枝傳回的資料, user.getIsLogin
跟 user.getCurrentUser
(這個在 user.reducer.ts 中定義)
因為我們的 selectors 相對單純,我們就先用一個檔案就好,以後複雜時,再來擴充
第三步:修改 src/app/store/selectors/index.ts
export * from './selectors';
下次我們要將元件改版,從原來使用服務的地方,改成使用 ngrx/store