前言
一個網(wǎng)站,通常都會包含公開頁面和保護頁面兩種,如果是OA或者企業(yè)應(yīng)用網(wǎng)站,甚至可能全部都是保護頁面,訪問者需要在進行身份認證后,才能正常的瀏覽相關(guān)頁面。
路由進階應(yīng)用
在上一篇 Angular 2.0 SPA應(yīng)用 - 從腳手架開始 (1) 文章中,我們介紹了如何從一個腳手架Angular 2.0開始,添加一個首頁和登錄頁面,并實現(xiàn)了相關(guān)的路由功能。
本文中,我們將會添加一個郵件發(fā)送頁面,同時,希望只有登錄用戶可以訪問此頁面。
- 路由守衛(wèi)(Route Guard)
添加AuthGuard,隨機返回True/False(分別為50%概率)。
import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private router: Router) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
console.log('AuthGuard#canActivate called');
if (this.checkLogin()) {
// l已登錄,返回Ture
console.log("AuthGuard: 用戶已登陸。");
return true;
}
// 未登陸,重定向URL到登錄頁面,包含返回URL參數(shù),然后返回False
this.router.navigate(['/login'], { queryParams: { returnUrl: state.url }});
return false;
}
private checkLogin(): boolean {
//隨機返回Ture /False
let loggedIn:boolean = Math.random() < 0.5;
if(!loggedIn){
console.log("AuthGuard: 用戶未登陸。");
}
return loggedIn;
}
}
- 郵件組件 (MailComponent)
//mail.component.ts
import {Component} from '@angular/core'
@Component({
selector: 'mail',
moduleId: __moduleName,
template: `
<div class="container" style="margin-top:100px;">
<h1>Mail Page</h1>
<div>
`
})
export class MailComponent {
}
- 修改app.ts
添加MailComponent和AuthGuard引用,添加MailComponent路由并對MailComponent使用AuthGuard守護。
......
import { MailComponent } from './mail/mail.component';
import { AuthGuard } from './login/auth.guard.ts';
const appRoutes: Routes = [
......
{ path: 'mail', component: MailComponent, canActivate: [AuthGuard] },
......
];
@NgModule({
......
declarations: [ App, HomeComponent, LoginComponent, MailComponent ],
providers: [ AuthGuard ],
......
})
新增或修改的代碼主要功能是:
- canActivate屬性聲明路由守衛(wèi)(Route Guard)
- providers屬性提供依賴注入(Dependency Injection)。
- 修改app.template.html
在原來的Home菜單旁邊,添加Mail菜單
<ul class="nav navbar-nav">
<li><a routerLink="home" routerLinkActive="active">首頁</a></li>
<li><a routerLink="mail" routerLinkActive="active">Mail</a></li>
</ul>
- canActivate屬性聲明路由守衛(wèi)(Route Guard)
- providers屬性聲明依賴注入(Dependency Injection)。
身份驗證
在上面的AuthGuard中,我們并沒有真正實現(xiàn)用戶身份的驗證,只是隨機返回True/False來模擬用戶已登錄或未登陸狀態(tài)下,訪問守護頁面時,路由導(dǎo)航應(yīng)有的反應(yīng)。
將驗證邏輯從AuthGuard分離,實現(xiàn)一個authenticationService,應(yīng)該包含以下功能:
- 是否已通過身份驗證 isAuth
- 登錄身份驗證 Login(username, password)
- 注銷當前登錄 Logout()
- ** 添加 src/auth/authentication.service.ts **
import { Injectable } from '@angular/core';
@Injectable()
export class AuthenticationService {
isAuth() {
if (localStorage.getItem('currentUser')) {
return true;
}
else { return false; }
}
login(username: string, password: string) {
if (username=='admin' && password=="admin") {
localStorage.setItem('currentUser', username);
return true;
}
else {
return false;
}
}
logout() {
// remove user from local storage to log user out
localStorage.removeItem('currentUser');
}
}
好吧,我必須承認我偷懶,現(xiàn)在還是假的驗證邏輯,登陸用戶名和密碼都是"admin",就通過驗證。這樣一來,我們可以先不管復(fù)雜的后端驗證邏輯,先修改并測試前端登錄界面。
- ** 修改 src/login/authGuard.ts **
import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { AuthenticationService } from '../auth/authentication.service.ts'
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private router: Router, private authService: AuthenticationService) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
console.log('AuthGuard#canActivate called');
if (this.authService.isAuth()) {
// l已登錄,返回Ture
return true;
}
// 未登陸,重定向URL到登錄頁面,包含返回URL參數(shù),然后返回False
this.router.navigate(['/login'], { queryParams: { returnUrl: state.url }});
return false;
}
}
......
- ** Login 組件**
login.template.html
<div class="container">
<div id="loginbox" style="margin-top:100px;" class="mainbox col-md-6 col-md-offset-3 col-sm-8 col-sm-offset-2">
<div class="panel panel-info">
<div class="panel-heading">
<div class="panel-title">Login</div>
</div>
<form name="form" (ngSubmit)="f.form.valid && login()" #f="ngForm" novalidate>
<div style="padding:30px" class="panel-body">
<div class="form-group" [ngClass]="{ 'has-error': f.submitted && !username.valid }">
<label for="username">Username</label>
<input type="text" class="form-control" name="username" [(ngModel)]="model.username" #username="ngModel" required />
<div *ngIf="f.submitted && !username.valid" class="help-block">Username is required</div>
</div>
<div class="form-group" [ngClass]="{ 'has-error': f.submitted && !password.valid }">
<label for="password">Password</label>
<input type="password" class="form-control" name="password" [(ngModel)]="model.password" #password="ngModel" required />
<div *ngIf="f.submitted && !password.valid" class="help-block">Password is required</div>
</div>
<div class="form-group">
<button [disabled]="loading" class="btn btn-primary">Login</button>
<img *ngIf="loading" src="data:image/gif;base64,R0lGODlhEAAQAPIAAP///wAAAMLCwkJCQgAAAGJiYoKCgpKSkiH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQJCgAAACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkECQoAAAAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkECQoAAAAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkECQoAAAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQJCgAAACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQJCgAAACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAkKAAAALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA=="
/>
</div>
</div>
</form>
</div>
</div>
</div>
login.component.ts
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { AuthenticationService } from '../auth/authentication.service';
@Component({
selector: 'login',
moduleId: __moduleName,
templateUrl: './login.template.html'
})
export class LoginComponent {
model: any = {};
loading = false;
returnUrl: string;
constructor(private route: ActivatedRoute, private router: Router, private authService: AuthenticationService) {}
ngOnInit() {
// reset login status
this.authService.logout();
// get return url from route parameters or default to '/'
this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
}
login() {
this.loading = true;
if (this.authService.login(this.model.username, this.model.password)) {
this.router.navigate([this.returnUrl]);
}
else {
this.loading = false;
}
}
}
由于LoginComponent.ts的構(gòu)造函數(shù)需要 AuthenticationService 依賴注入,我們需要回過頭去,修改app.ts文件,加入相關(guān)代碼,這兒就不詳細寫出來,請讀者自行摸索一下。
總結(jié)
在本文中,學(xué)習(xí)了Route Guard,加入身份認證,登錄界面做了修改,基本可以使用了,還多次使用依賴注入。如果你對這些知識點還有不清楚的地方,建議可以到 Angular 2.0 查閱文檔。
Plunker Demo
下篇預(yù)告
在兩篇文章中,基本完成了Angular SPA常用的功能介紹,貌似太快了一點點。
下篇寫啥呢,有點失去方向,身份認證繼續(xù)發(fā)展,就是引入后端服務(wù)的時候,要么介紹一下Interception和mockBackendService技術(shù),如果你有什么建議和意見,不妨告訴我,謝謝!