前言
在Angular項(xiàng)目中尔店,界面跳轉(zhuǎn)是在所難免的事情,而跳轉(zhuǎn)界面最基礎(chǔ)的就是用戶的登錄與登出操作。
比如我們經(jīng)營(yíng)一個(gè)社交網(wǎng)站座掘,首先是要允許持有合法credentials
的用戶登錄,用戶可以用自己的賬號(hào)發(fā)表動(dòng)態(tài)柔滔,與他人交流溢陪;其次當(dāng)用戶在編輯自己的動(dòng)態(tài)過(guò)程中不小心點(diǎn)到了返回,退出登錄睛廊,瀏覽器的后退...可能我們就需要善意的提醒一下形真,您可是還有未保存的修改。
于是針對(duì)以上兩種情況:
- 什么人可以訪問(wèn)超全,我們使用
canActivate
- 是么時(shí)候可以退出訪問(wèn)咆霜,我們使用
canDeactivate
前要:Angular的Router
如果你使用過(guò)angular-cli
的話,app.module.ts
和app.routing.module
這兩個(gè)文件相信都比較熟悉嘶朱,app.module.ts
這里不多說(shuō)蛾坯,你項(xiàng)目的declarations
,imports
還有providers
等等都得在這里引入好先疏遏,當(dāng)然脉课,也包括了這個(gè)app.routing.modul
救军。
一般來(lái)說(shuō),我們的aap.routing.module
的寫(xiě)法都比較統(tǒng)一
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
...
const routes: Routes = [
{
path: '/login',
component: '...',
canActivate: [AuthGuard],
},
{
path: 'logout',
component: '...',
canDeactivate: [UnsavedGuard],
}
...
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {
}
很容易理解倘零,我們把整個(gè)項(xiàng)目的路由路徑以及他對(duì)應(yīng)的組件都寫(xiě)進(jìn)了一個(gè)routes
數(shù)組唱遭,在app.module.ts
里面引用后,就可以根據(jù)對(duì)應(yīng)的路徑訪問(wèn)對(duì)應(yīng)的界面视事。
我們來(lái)仔細(xì)看一下Routes
這個(gè)Route[]
的東西是個(gè)啥樣子胆萧,它可以有哪些參數(shù)呢:
interface Route {
path?: string
pathMatch?: string
matcher?: UrlMatcher
component?: Type<any>
redirectTo?: string
outlet?: string
canActivate?: any[]
canActivateChild?: any[]
canDeactivate?: any[]
canLoad?: any[]
data?: Data
resolve?: ResolveData
children?: Routes
loadChildren?: LoadChildren
runGuardsAndResolvers?: RunGuardsAndResolvers
}
關(guān)于Route
的詳細(xì)內(nèi)容可以查看官網(wǎng)的文檔Angular/Router。從上可以看到俐东,我們的主人公canActivate
和canDeactivate
就是在每個(gè)Route
的聲明的時(shí)候給加上的跌穗。具體怎么操作,我們繼續(xù)往下看虏辫。
前門(mén):訪問(wèn)許可canActivate
我們先來(lái)看angular
對(duì)canActivate
的注釋?zhuān)?/p>
An array of dependency-injection tokens used to look up `CanActivate()` handlers, in order to determine if the current user is allowed to activate the component. By default, any user can activate.
簡(jiǎn)單來(lái)說(shuō)蚌吸,我們?cè)趧?chuàng)建一個(gè)Route
的時(shí)候默認(rèn)所有人都可以訪問(wèn),但是有些機(jī)密的東西砌庄,不是每個(gè)人都可以看到的羹唠,所以要加一個(gè)看門(mén)人canActivate
來(lái)給我們把關(guān),它呢需要你給他制定一個(gè)規(guī)則娄昆,你把這個(gè)規(guī)則制訂好作為一個(gè)數(shù)組傳給他就大功告成了佩微。
而我們的主要工作就是制定一個(gè)符合你的業(yè)余需求的門(mén)禁規(guī)則
,它需要繼承canActivate
接口萌焰,其定義如下:
interface CanActivate {
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree
}
我們來(lái)一個(gè)例子哺眯,我們網(wǎng)站上有個(gè)人主頁(yè),但是個(gè)人主頁(yè)訪問(wèn)的前提是你得成功登錄了扒俯,所以這里可以對(duì)個(gè)人主頁(yè)
這個(gè)界面請(qǐng)一個(gè)AuthGuard
奶卓,代碼如下:
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { of } from 'rxjs';
import { AuthService } from '....'
export class AuthGuard implements CanActivate {
constructor(private router: Router, private auth: AuthService) {
}
// 復(fù)寫(xiě)canActivate方法
public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
return this.auth.isLoggedIn();
}
}
很簡(jiǎn)單的驗(yàn)證,自己寫(xiě)一個(gè)對(duì)auth
對(duì)驗(yàn)證服務(wù)撼玄,然后這里引入進(jìn)行驗(yàn)證就ok夺姑,記住它需要返回的是一個(gè)observable
或者Promise
,對(duì)應(yīng)的方法也應(yīng)該如此掌猛,這里對(duì)于canActivate
返回true
即允許訪問(wèn)盏浙,false
反之。
然后最后一步加入到你的route
中:
{
path: 'userIndex/:userId',
component: 'UserIndexComponent',
canActivate: [AuthGuard],
}
Well done荔茬,現(xiàn)在你的用戶主頁(yè)就需要通過(guò)你制定的登錄驗(yàn)證才能訪問(wèn)了只盹。
后門(mén):離開(kāi)許可canDeactivate
正如上文說(shuō)到的,如果一個(gè)用戶在編輯頁(yè)面內(nèi)容的過(guò)程中想要跳轉(zhuǎn)到其他界面兔院,那么他當(dāng)前做出的更改應(yīng)該如何處理呢殖卑?我們給他直接保存或是不丟棄感覺(jué)都不太make sense,所以大部分做法是提示用戶有未保存的更改坊萝,他自己決定是否要放棄保存離開(kāi)界面或是繼續(xù)編輯孵稽。
因此這個(gè)離開(kāi)界面的時(shí)候我們就可以安排一個(gè)后門(mén)守門(mén)員LeaveGuard
來(lái)幫我們提醒準(zhǔn)備離開(kāi)的人许起,他同樣需呀繼承canDeactivate
接口,其定義如下:
interface CanDeactivate<T> {
canDeactivate(component: T, currentRoute: ActivatedRouteSnapshot, currentState: RouterStateSnapshot, nextState?: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree
}
注意:
canDeactivate
的第一個(gè)參數(shù)是component
菩鲜,這個(gè)參數(shù)就是你需要進(jìn)行退出驗(yàn)證的組件园细。
舉個(gè)例子,用戶在修改自己的個(gè)人資料的時(shí)候接校,忘了點(diǎn)保存就點(diǎn)到了界面中的返回按鈕猛频,那么這個(gè)時(shí)候我們LeaveGuard
的代碼如下:
leave.guard.ts
import { Injectable } from '@angular/core';
import { CanDeactivate } from '@angular/router';
import { UserDetailComponent } from '../user-detail.component';
import { of } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class LeaveGuard implements CanDeactivate<UserDetailComponent> {
public canDeactivate(component: UserDetailComponent): Observable<boolean> {
if (component.onPageLeaving && typeof(component.onPageLeaving) === 'function') {
return component.onPageLeaving();
} else {
return of(true);
}
}
}
user-detail.component
...
public onPageLeaving(): Observable<boolean> {
// if the form has been edited
if (this.formGroup.dirty) {
// may be you can come up an alert for user first
...
if (yes) { return of(true); }
else { return of(false); }
} else {
return of(true);
}
}
// be careful, you need to update the form to be Pristine after updated
public updateFile() {
...
this.formGroup.markAsPristine();
}
...
同樣,最后在你的route
中加入:
{
path: 'userDetail/:userId',
component: 'UserDetailComponent',
canDeactivate: [LeaveGuard],
}
在代碼中提示了一點(diǎn)蛛勉,就是說(shuō)我們每當(dāng)文檔被修改后鹿寻,即dirty === true
了,我們就需要去提醒用戶是否要繼續(xù)跳轉(zhuǎn)诽凌,而當(dāng)用戶更新了這個(gè)文件后毡熏,我們應(yīng)該重新把這個(gè)文件標(biāo)記為Pristine
,這樣就不會(huì)發(fā)現(xiàn)生為哈我保存了還是dirty
的情況侣诵。
至此痢法,我們的后門(mén)門(mén)衛(wèi)也就安排好了。
提醒一下
有些時(shí)候我們經(jīng)常在界面的上方固定一個(gè)控制面板(Dashboard)杜顺,而通過(guò)這個(gè)控制面板可以直接的退出登錄财搁,那么在這個(gè)地方我們要特別注意一下,deActivate
和canActivate
的觸發(fā)都是基于route
的改變躬络,一般來(lái)說(shuō)就是使用了navigate
或者navagateByUrl
尖奔,redirect
之類(lèi)的方法改變了你的當(dāng)前所在路由。
經(jīng)常我們對(duì)于logout()
方法主要做兩件事:
- 刪除用戶當(dāng)前的登錄信息 => token for example;
- 刷新界面/返回登錄界面
那么我們可以看到洗鸵,當(dāng)我們點(diǎn)擊了登出按鈕越锈,后臺(tái)就會(huì)刪除用戶的登錄信息仗嗦,那么即使我們界面被后門(mén)門(mén)衛(wèi)不讓走膘滨,但是實(shí)際上我們的數(shù)據(jù)已經(jīng)沒(méi)了,甚至你可以看到界面里被動(dòng)態(tài)加載后數(shù)據(jù)空空如也稀拐,這個(gè)時(shí)候即使你不離開(kāi)界面火邓,你的所有操作也都沒(méi)法實(shí)現(xiàn)了。
因此針對(duì)這個(gè)問(wèn)題的出現(xiàn)德撬,有一個(gè)我采用的方法就是铲咨,額外增加一個(gè)logout.component
。就是說(shuō)用戶點(diǎn)擊登出
按鈕也不會(huì)理解登出蜓洪,而是首先會(huì)跳轉(zhuǎn)到logout
界面纤勒,這個(gè)界面可以是一個(gè)短暫的加載界面,因?yàn)橥鶃?lái)說(shuō)隆檀,登出也是需要后臺(tái)的一次response
摇天,所以一個(gè)簡(jiǎn)單的loading界面也不會(huì)顯得很奇怪粹湃,同時(shí)最大的改善就是我們會(huì)第一時(shí)間就觸發(fā)我們的門(mén)衛(wèi)機(jī)制,防止了登出操作的錯(cuò)誤進(jìn)行泉坐。
當(dāng)然为鳄,如果你有更好的方法,有更多的問(wèn)題腕让,歡迎討論孤钦。
至此,本文完結(jié)纯丸。