iT邦幫忙

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

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

[ngrx/store-19] Angular 網站實例 - 使用者服務篇

Angular 網站實例 - 使用者服務篇

今天開始 今天完成

今天的目標:完成使用者服務,讓使用者登入畫面能夠透過使用者服務來連結後端
https://ithelp.ithome.com.tw/upload/images/20180104/201035744ecOD8z1bu.jpg

設定使用者服務 (User Service)

第一步:在使用者模組下建立目錄 /src/app/user/service

mkdir service

第二步:產生使用者服務

ng generate service user --module user 

使用 --module userangular-cli 會主動將這個服務加入模組成為 provider,這個服務是全域的服務,也就是所有的元件 (component) 都可以使用。

第三步:加入後端網址,在 src/app/share 下加入

ng generate class app.config

第四步:修改 app.config.ts

export class AppConfig {
    public readonly apiUrl = 'http://localhost:3000/api';
}

第五步:加入 share 模組

// ... 省略
import { AppConfig } from './app.config';

// ... 省略 
@NgModule({
// ...
    providers: [AppConfig]

})

第六步: share 模組加入 indext.ts

export * from './share.module';
export * from './app.config';

設定介面 (Interface)

我們來增加兩個介面:使用者跟後端回應的介面
第一步:先造一個目錄 src/app/models

mkdir models

第二步:用 angular-cli 產生使用者介面

ng generate interface user

第三步:修改 user.ts

export interface User {
    username: string;
    password?: string;
    rememberMe?: boolean;
}

第四步:用 angular-cli 產生後端回應介面

ng generate interface response

第五步:修改 response.ts

export interface Response {
    success: boolean;
    payload?: any;
}

第六步:建立 index.ts 在 src/app/models 下,這樣在元件做 import 時,只要指到這個目錄即可
src/app/models/index.ts

export * from './user';
export * from './response';

加入 HttpClient 模組

在 src/app/user/user.module.ts 加入

//... 省略 
import { HttpClientModule } from '@angular/common/http';
// ... 省略
@NgModule({
    imports: [
        CommonModule,
        UserRoutingModule,
        ShareModule,
        HttpClientModule
    ],
    declarations: [LoginComponent],
    providers: [UserService]
})
export class UserModule { }

使用者服務

這個服務要提供:使用者登入狀態及使用者名稱,我們會用到 BehaviorSubject 這個 Observable 來做,因為這些狀態隨時會改變。 直接看程式

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 { BehaviorSubject } from 'rxjs/BehaviorSubject';
import 'rxjs/add/operator/map';
import { of } from 'rxjs/observable/of';

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

    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 });
    }
    login(loginData): Observable<boolean> {
        return this.loginServer(loginData)
            .map((res: Response) => {
                if (res.success) {
                    this.loginStatus.next(true);
                    this.currentUser.next(loginData.username);
                    return true;
                } else {
                    console.log('can not login');
                    return false;
                }
            },
            (err: HttpErrorResponse) => {
                if (err.error instanceof Error) {
                    console.log('client-side error');
                } else {
                    console.log('server-side error');
                }
                return of(false);
            })
    }
    logout() {
        this.loginStatus.next(false);
        this.currentUser.next(null);
    }
    getLoginStatus(): Observable<boolean> {
        return this.loginStatus;
    }
     getCurrentUser(): Observable<User> {
        return this.currentUser;
    }
}
  1. 建立 loginStatus$ 跟 currentUser$ BehaviorSubject
  2. 導入 HttpClient 跟 AppConfig
  3. loginServer() 使用 http.post() 跟後端連結。
  4. login()使用 loginServer() subscribe 後端的回應,如果成功,將 loginStatus$currentUser$ push 給訂閱者 (Subscribers)
  5. logout()loginStatus$currentUser$ 回到原來狀態
  6. 最後提供兩個介面 getLoginStatus()getCurrentUser() 給元件使用

完成使用者服務後,我們來更新一下它的用戶: loginnavbar 元件

更新使用者登入元件 (login)

[ngrx/store-17] Angular 網站實例 - 使用者登入篇已經完成登入畫面,我們只要在做 submit() 時呼叫 login() 函數

//... 省略
import { UserService } from '../service/user.service';
import { MatSnackBar } from '@angular/material';
//...
 constructor(
        private fb: FormBuilder,
        private userService: UserService,
        private snackbar: MatSnackBar
    ) { }

//...
login() {
        this.userService.login(this.form.value)
            .subscribe(res => {
                if (res) {
                    this.snackbar.open('登入成功', 'OK', { duration: 3000});
                } else {
                    this.snackbar.open('請檢查使用者名稱及密碼', 'OK', {duration: 3000});
                }
            })
    }

導入 UserService,將表格資料傳給 UserService,登入成功時,用 @angular/materailsnackbar顯示成功訊息,否則顯示失敗訊息。使用者可以按 OK 來取消訊息或者等 3 秒就會自動消失。

更新瀏覽列 (Navbar)

第一步:修改 navbar.component.ts

直接看程式

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';

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

import { UserService } from '../user/service/user.service';
import { User } from '../models';

@Component({
    selector: 'app-navbar',
    templateUrl: './navbar.component.html',
    styleUrls: ['./navbar.component.css']
})
export class NavbarComponent implements OnInit {
    login$: Observable<boolean>;
    user$: Observable<User>;
    constructor(
        private userService: UserService,
        private router: Router
    ) { }
    ngOnInit() {
        //this.login$ = Observable.of(true);
        this.login$ = this.userService.getLoginStatus();
        this.user$ = this.userService.getCurrentUser();
    }
    logout() {
        //this.login$ = Observable.of(false);
        this.userService.logout();
        this.router.navigate(['/'])
    }
}
  1. 加入 user$ Observable
  2. 導入 RouterUserService
  3. ngOnInit() 設定 login$user$ 兩個 Observable

我們也稍微修改一下畫面,在瀏覽列加入登入的使用者名稱

第二步:加入新的 Material

先加入 mat-menumat-icon 跟前面的 matSanckBarshare module

//... 省略
import { MatInputModule, MatIconModule, MatMenuModule, MatSnackBarModule } from '@angular/material';

// ... 省略
@NgModule({
    imports: [
    // ...
        MatInputModule,
        MatMenuModule,
        MatSnackBarModule,
    //...
    ],
    exports: [
    //...
        MatInputModule,
        MatMenuModule,
        MatSnackBarModule,
        //...
        ]
})

第三步:修改 navbar.component.html

如下:

<mat-toolbar  [style.background] = "(login$ | async) ? 'darkviolet' : 'seagreen'" class="navbar">
  <a mat-button routerLink="/">洋蔥投顧</a>
  <span class="to-right"></span>
  <a mat-button *ngIf="!(login$| async)" routerLink="/user/login">會員登入</a>
  <a mat-button *ngIf="login$ | async" routerLink="">會員中心</a>
  <button *ngIf="user$ | async " mat-button [matMenuTriggerFor]="userMenu">
    <span>{{user$ | async}}</span> <mat-icon>arrow_drop_down</mat-icon>
  </button>      
</mat-toolbar>
<!-- User sub menu -->
<mat-menu x-position="before" #userMenu="matMenu">
  <button mat-menu-item >帳號</button>
  <button mat-menu-item (click)="logout()" >會員登出</button>
</mat-menu>

當有使用者的時候,接下一個子選單 (sub menu),裡面包含 帳號,會員登出

完成後截圖如下
登入成功
https://ithelp.ithome.com.tw/upload/images/20180104/20103574BAykxY8aw8.png
登入失敗
https://ithelp.ithome.com.tw/upload/images/20180104/20103574OmK40eIsGR.png

登出
https://ithelp.ithome.com.tw/upload/images/20180104/20103574KGjC1SX4qG.png

到這裡,我們大概完成了使用者登入的動作,但這裡有一個問題,如果使用者重新刷瀏覽器,會重回未登入狀態,我們下次來解決這個問題。


上一篇
[ngrx/store-18] 網站實例 - 用 express 做後端
下一篇
[ngrx/store-20] Angular 網站實例 - 記得我篇
系列文
ngrx/store 4 學習筆記30

尚未有邦友留言

立即登入留言