iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 26
0
Modern Web

ngrx/store 4 學習筆記系列 第 26

[ngrx/store-26] ngrx/store 之會員篇 Effects, Selector

ngrx/store 之會員篇 Effects, Selector

今天開始 今天完成

今天目標:完成會員部分的 ngrx/store
複習一下我們的目標
https://ithelp.ithome.com.tw/upload/images/20180111/2010357490Ic1sPrFG.jpg

複習一下 [ngrx/store-7] 純函數 (Pure Function), Effects 在 store 中的角色就是要處理有“副作用”的部分,例如跟後端聯繫,讀出寫入 Localstorage 等等,不是純函數的部分,在會員篇的狀態中, Login, Logout, GetUser 都會有“副作用”,所以也都會用到 Effects,

會員篇 - 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))),
            )
        })
    )
}
  1. constructor() 導入動作 Actions(監聽用),使用者服務 UserService(跟後端連結)跟工具服務 UtilsService (寫入讀出 Localstorage)
  2. Effect 函數使用 this.action$.ofType() “監聽“ 動作 (Action),處理完“副作用“的部分後,最後會輸出 Observable<Action> 另外的動作
  3. loginEffect$ 監聽 "LOGIN" 的動作,一聽到這個動作,先從動作的payload取出資料,之前我們在定義
export class LoginAction implements Action {
    readonly type = LOGIN;

    constructor(public payload: User) { }
}

時,已經定義了 payloadUser 型態,接著用 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
    }
//...
  1. logoutEffect$ 監聽 "LOGOUT" 的動作,簡單的做清除 token
  2. getUserEffect$ 監聽 "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

會員篇 - Selector

有人形容 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.getIsLoginuser.getCurrentUser (這個在 user.reducer.ts 中定義)

因為我們的 selectors 相對單純,我們就先用一個檔案就好,以後複雜時,再來擴充
第三步:修改 src/app/store/selectors/index.ts

export * from './selectors';

下次我們要將元件改版,從原來使用服務的地方,改成使用 ngrx/store


上一篇
[ngrx/store-25] ngrx/store 設定篇及會員篇之 Action, Reducer
下一篇
[ngrx/store-27] ngrx/store 之會員篇
系列文
ngrx/store 4 學習筆記30

尚未有邦友留言

立即登入留言