iT邦幫忙

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

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

[ngrx/store-20] Angular 網站實例 - 記得我篇

Angular 網站實例 - 記得我篇

今天開始 今天完成

上次我們已經成功連上了後端,但是只要使用者ㄧ重新刷瀏覽器,又會回到未登入狀態,今天我們就來讓前端記住登入狀態。

在前端要記住一些資料,我們會用到 cookielocal storage或者 session storage,至於它們的不同點,可以參考 stackoverflow這篇文章,我們先來做一個有關 local storage 的服務 (service)

公用服務 (utility service)

第一步:造一個 src/app/services 目錄

mkdir servcies

第二步: 用 angular-cli 產生服務並自動註冊到 app.module.ts

ng generate service utils --module app 

第三步:加入 jsonwebtoken 來判斷 token 是否逾期

npm install @auth0/angular-jwt --save

記得使用這個套件,其它套件不能夠跟新的 HttpClientModule 相容

第四步:修改 app.module.ts,導入 JwtModule

// ... 省略
import { HttpClientModule } from '@angular/common/http';
import { JwtModule } from '@auth0/angular-jwt';

//... 省略
@NgModule({
    //... 
    imports: [
    //...
        HttpClientModule,
        JwtModule.forRoot({
            config: {
                tokenGetter: () => {
                    return localStorage.getItem('access_token');
                },
                whitelistedDomains: ['localhost:3000']
            }
        })
    ],
//...

記得同時導入 HttpClientModuleJwtModule

第五步:修改 utils.service.ts

import { Injectable } from '@angular/core';
import { JwtHelperService } from '@auth0/angular-jwt';
export const TOKEN = 'access_token';
@Injectable()
export class UtilsService {
    constructor(
        private jwtHelper: JwtHelperService
    ) { }

    isTokenExpired(token: string = TOKEN): boolean {
        let jwtStr = this.getToken(token);
        if (jwtStr) {
            return this.jwtHelper.isTokenExpired(jwtStr);  // token expired?
        } else {
            return true;        // no token 
        }
    }

    writeToken(value: string, token: string = TOKEN) {
        localStorage.setItem(token, value);
    }

    getToken(token: string = TOKEN) {
        return localStorage.getItem(token);
    }

    removeToken(token: string = TOKEN) {
        if (this.getToken(token)) {
            localStorage.removeItem(token);
        }
    }
}

預設的 token 的關鍵(key) 為 access_token,為了可以寫入不同的關鍵,我們參數保留了 token 這個關鍵。
第六步: 將 index.ts 加入 src/app/services

export * from './utils.servcie';

修改使用者服務

第一步:導入公用服務

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

@Injectable()
export class UserService {
    loginStatus = new BehaviorSubject<boolean>(false);
    currentUser = new BehaviorSubject<User>(null);
    constructor(
        private http: HttpClient,
        private appConfig: AppConfig,
        private utils: UtilsService
    ) { }
// ... 省略

第二步:修改 login() 程式,後端驗證成功會傳回關鍵值 (value),如果使用者有勾選 記得我,我們用公用服務將它寫入 localstorage

// ... 省略
login(loginData): Observable<boolean> {
        return this.loginServer(loginData)
            .map((res: Response) => {
                if (res.success) {
                    this.loginStatus.next(true);
                    this.currentUser.next(loginData.username);
                    if (loginData.rememberMe) {
                        this.utils.writeToken(res.payload);
                    }
                    return true;
                } else {
                    return false;
                }
            },
//... 省略

第三步:修改 logout(),登出時將 token 清掉

   logout() {
        this.loginStatus.next(false);
        this.currentUser.next(null);
        this.utils.removeToken();
    }

第四步:加入 checkuser() 函數

// when startup
    checkUser(): Observable<boolean> {
        if (!this.utils.isTokenExpired()) {
            this.loginStatus.next(true);
            return of(true);
        } else {
            console.log('no token or token is expired');
            this.utils.removeToken();
            return of(false);
        }
    }

這個函數馬上會用到,基本上是先檢查 token 有沒有過期,沒有的話將 loginStatus 設為真

第五步:確認
做登入後檢查是否 localstorage 已經寫入,正常狀態如截圖
https://ithelp.ithome.com.tw/upload/images/20180105/20103574ORJdKJBSWj.png

啟動服務 (startup service)

接下來我們要在瀏覽器重新啟動時來檢查這個 token,我們先來做一個服務
第一步:產生 startup.servcie.ts 在 src/app/services 下

ng generate servcie startup --module app

第二步:修改程式

import { Injectable, Injector } from '@angular/core';
import { Router } from '@angular/router';
import { UtilsService } from '.';
import { UserService } from '../user/service/user.service';
@Injectable()
export class StartupService {
    constructor(
        private injector: Injector,
        private utils: UtilsService,
        private userService: UserService
    ) { }
    load(): Promise<any> {
        return new Promise((resolve, reject) => {
            return this.userService.checkUser()
                .subscribe(res => {
                    if (res) {
                        setInterval(() => {
                            this.checkStatus();
                        }, 1000 * 60 * 5)    // check current status every 5 min
                    }
                    resolve(res);
                }, err => {
                    console.log(err);
                    reject(err);
                });
        });
    }
    checkStatus() {
        if (this.utils.isTokenExpired()) {   // if token expired
            this.userService.logout();
            const router = this.injector.get(Router);
            router.navigate(['/']);
            console.log('logout due to token expired');
        }
    }
}
  1. startup 服務一定要建置一個 Promise 函數回給系統,這裡我們用 load()
  2. load() 函數中利用使用者服務的 checkuser() 函數,如果 token 還沒過期,使用者服務的 loginStatus 會變成 true
  3. 每五分鐘檢查一下狀態 checkStatus(),如果逾期,做一個 logout() 的動作然後將頁面導回首頁
  4. 因為在 Startup 時無法直接用 DI (Depency Injection) Router,請參考 stackoverflow,我們需要用到Injector 來導入Router

第三步: 修改app.module.ts

//... 省略
import { NgModule, APP_INITIALIZER, Injector } from '@angular/core';
//... 省略
export function startupServiceFactory(startupService: StartupService): Function { return () => startupService.load(); }
// ... 省略
@NgModule({
    // ...
  providers: [
        UtilsService,
        StartupService,
        {
            provide: APP_INITIALIZER,
            useFactory: startupServiceFactory,
            deps: [StartupService, Injector],
            multi: true
        }
    ],
//... 省略

好了,現在當您登入系統,回到首頁,重新刷瀏覽器,應該會留在登入狀態(瀏覽列底色),但是看不到使用者名稱,也沒辦法登出,我們下次來解決這個問題。


上一篇
[ngrx/store-19] Angular 網站實例 - 使用者服務篇
下一篇
[ngrx/store-21] Angular 網站實例 - 記得我之二篇
系列文
ngrx/store 4 學習筆記30

尚未有邦友留言

立即登入留言