iT邦幫忙

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

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

[ngrx/store-27] ngrx/store 之會員篇

ngrx/store 之會員篇

今天開始 今天完成

今天的目標:將會員有關的元件及服務從使用者服務轉為向 ngrx/store 做 dispatch 跟 select

會員登入元件

第一步:修改 src/app/user/login/login.component.ts

// ... 省略
import { Store } from '@ngrx/store';
import * as fromStore from '../../store';

//... 省略
export class LoginComponent implements OnInit {
    public form: FormGroup;
    constructor(
        private fb: FormBuilder,
        private store: Store<fromStore.State>,
        private router: Router,
        private snackbar: MatSnackBar
    ) { }
    login() {
        this.store.dispatch(new fromStore.LoginAction(this.form.value));
        this.store.select(fromStore.getIsLogin)
            .subscribe(res => {
                if (res) {
                    this.snackbar.open('登入成功', 'OK', { duration: 3000 });
                    this.router.navigate(['/member']);
                } else {
                    this.snackbar.open('請檢查使用者名稱及密碼', 'OK', { duration: 3000 });
                }
            })
    }
}
  1. 將 UserService 刪除換成 Store,並且 import 我們之前寫的 store 成為 fromStore
  2. constructor 導入 Store
  3. 將原本 userService 的部分換成 store.disptch(new fromStore.LoginAction(),接著用 store.select(fromStore.getIsLogin) 來選擇狀態樹中 login 的狀態

檢查一下瀏覽器,一且順利的話,畫面截圖如下
https://ithelp.ithome.com.tw/upload/images/20180112/20103574le6XjStaRk.png
可以看到 Redux devtools 已經看到會員狀態已經改變,但是頁面還沒轉到會員,接下來

修改路由防護 (Auth.Guard)

修改 src/app/guards/auth.guards.ts

//...省略
import { Store } from '@ngrx/store';
import * as fromStore from '../store';
import { tap, take } from 'rxjs/operators';
import { of } from 'rxjs/observable/of';

@Injectable()
export class AuthGuard implements CanActivate, CanLoad {
    loginStatus$: Observable<boolean>
    constructor(
        private store: Store<fromStore.State>,
        private router: Router
    ) {
        this.loginStatus$ = store.select(fromStore.getIsLogin);
    }
    //... 省略

一樣將使用者服務刪除,導入 Store,在 constructor 將 loginStatus$ 變為 store.select(fromStore.getIsLogin) 就完成

修改瀏覽列

現在登入後會轉到會員畫面,但是瀏覽列還沒變,我們來修改一下
src/app/navbar/navbar.component.ts

//... 省略
import { Store } from '@ngrx/store';
import * as fromStore from '../store';
import { User } from '../models';
//... 省略
export class NavbarComponent implements OnInit {
    login$: Observable<boolean>;
    user$: Observable<string>;
    constructor(
        private store: Store<fromStore.State>,
        private router: Router
    ) { }
    ngOnInit() {
        this.login$ = this.store.select(fromStore.getIsLogin);
        this.user$ = this.store.select(fromStore.getCurrentUser);
    }
    logout() {
        this.store.dispatch(new fromStore.LogoutAction());
        this.router.navigate(['/'])
    }
}
  1. 將使用者服務換成 Store
  2. 原來的 login$user$ 換成用 store.select()
  3. logout 時用 store.dispatch(new fromStore.LogoutAction()) 來改變狀態

接下來,修改開始服務

修改開始服務

src/app/services/startup.service.ts

//... 省略
import { UtilsService } from '.';
import { Store } from '@ngrx/store';
import * as fromStore from '../store';

@Injectable()
export class StartupService {
    constructor(
        private injector: Injector,
        private utils: UtilsService,
        private store: Store<fromStore.State>
    ) { }
    
    load(): Promise<any> {
        return new Promise((resolve, reject) => {
            if (!this.utils.isTokenExpired()) {
                this.store.dispatch(new fromStore.GetUserAction());
                return this.store.select(fromStore.getIsLogin)
                    .pipe(
                    filter(status => status)
                    )
                    .subscribe(res => {
                        if (res) {
                            setInterval(() => {
                                this.checkStatus();
                            }, 1000 * 60 * 5)    // check current status every 5 min
                        }
                        resolve(res);
                    }, err => {
                        console.log(err);
                        reject(err);
                    });
            } else {
                resolve('no token or token expired');
            }
        });
    }
    checkStatus() {
        if (this.utils.isTokenExpired()) {   // if token expired or not exist
            this.store.dispatch(new fromStore.LogoutAction());
            const router = this.injector.get(Router);
            router.navigate(['/']);
        }
    }
}
  1. 刪除使用者服務換成 Store
  2. load() 加入判斷 token 有沒有過期
  3. 使用 store.dispatch(new fromStore.GetUserAction()) 來從後端取得使用者資訊,這裡是非同步,我們用 filter 來等後端資訊完成,因為 token 未過期,所以 status 一定要是真
  4. 每五分鐘檢查,checkStatus() 當 token 過期時,使用 store.dispatch(new fromStore.LogoutAction()) 來改變狀態
    最後修改使用者服務

修改使用者服務

src/app/user/service/user.service.ts

import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';

import { AppConfig } from '../../share';
import { User, Response } from '../../models';

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import { of } from 'rxjs/observable/of';

import { UtilsService } from '../../services';

@Injectable()
export class UserService {
    constructor(
        private http: HttpClient,
        private appConfig: AppConfig,
        private utils: UtilsService
    ) { }

    loginServer(loginData): Observable<Response> {
        let username = loginData.username.trim();
        let password = loginData.password.trim();
        return this.http.post<Response>(this.appConfig.apiUrl + '/users/authenticate', { username: username, password: password });
    }
    // get user from server
    getUserFromServer(): Observable<User> {
        if (!this.utils.isTokenExpired()) {
            const token = this.utils.getToken();
            return this.http.post(this.appConfig.apiUrl + '/users/currentUser', { 'token': token })
                .map((res: Response) => {
                    if (res.success) {
                        return { username: res.payload };
                    } else {
                        return null;
                    }
                })
        } else {
            return of(null);
        }
    }
}
  1. 上面是完整的使用者服務,我們將所有有關狀態的部分全部刪除,只剩下跟後端連結的部分 loginServer()getUserFromServer()
  2. 修改一下 getUserFromServer(),這裡有一個 Bug, 應該要回 { username: res.payload } 而不是 res.payload 因為後端的 res.payload 只是一個 string

我們已經完成會員的部分,也就是所有跟會員有關的元件,現在只對 Store,不再經由使用者服務。

接下來在做跟報告有關的元件之前,也們先來看一下路由的部分,路由 (Router) 也可以當成是狀態的一部份,因為在報告的模組中,我們會用到 /member/report/3 這樣的路徑,而之前我們用報告服務來抓這個單獨的報告,現在我們想從 Store 的狀態樹中,結合報告狀態跟路由狀態來取得這份單獨的報告,所以我們先來做路由 (Router) 的 ngrx/store


上一篇
[ngrx/store-26] ngrx/store 之會員篇 Effects, Selector
下一篇
[ngrx/store-28] ngrx/store 之路由篇
系列文
ngrx/store 4 學習筆記30

尚未有邦友留言

立即登入留言