今天的目標:完成使用者服務,讓使用者登入畫面能夠透過使用者服務來連結後端
第一步:在使用者模組下建立目錄 /src/app/user/service
mkdir service
第二步:產生使用者服務
ng generate service user --module user
使用 --module user
,angular-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';
我們來增加兩個介面:使用者跟後端回應的介面
第一步:先造一個目錄 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';
在 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;
}
}
BehaviorSubject
loginServer()
使用 http.post()
跟後端連結。login()
使用 loginServer()
subscribe 後端的回應,如果成功,將 loginStatus$
跟 currentUser$
push 給訂閱者 (Subscribers)logout()
將 loginStatus$
跟 currentUser$
回到原來狀態getLoginStatus()
跟 getCurrentUser()
給元件使用完成使用者服務後,我們來更新一下它的用戶: login
跟 navbar
元件
[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/materail
的 snackbar
顯示成功訊息,否則顯示失敗訊息。使用者可以按 OK
來取消訊息或者等 3 秒就會自動消失。
直接看程式
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(['/'])
}
}
user$
ObservableRouter
跟 UserService
ngOnInit()
設定 login$
跟 user$
兩個 Observable我們也稍微修改一下畫面,在瀏覽列加入登入的使用者名稱
先加入 mat-menu
,mat-icon
跟前面的 matSanckBar
到 share
module
//... 省略
import { MatInputModule, MatIconModule, MatMenuModule, MatSnackBarModule } from '@angular/material';
// ... 省略
@NgModule({
imports: [
// ...
MatInputModule,
MatMenuModule,
MatSnackBarModule,
//...
],
exports: [
//...
MatInputModule,
MatMenuModule,
MatSnackBarModule,
//...
]
})
如下:
<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),裡面包含 帳號
,會員登出
。
完成後截圖如下
登入成功
登入失敗
登出
到這裡,我們大概完成了使用者登入的動作,但這裡有一個問題,如果使用者重新刷瀏覽器,會重回未登入狀態,我們下次來解決這個問題。