Angular的守門(mén)員canActivate與canDeactivate

前言

在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ì)以上兩種情況:

  1. 什么人可以訪問(wèn)超全,我們使用canActivate
  2. 是么時(shí)候可以退出訪問(wèn)咆霜,我們使用canDeactivate

前要:Angular的Router

如果你使用過(guò)angular-cli的話,app.module.tsapp.routing.module這兩個(gè)文件相信都比較熟悉嘶朱,app.module.ts這里不多說(shuō)蛾坯,你項(xiàng)目的declarationsimports還有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。從上可以看到俐东,我們的主人公canActivatecanDeactivate就是在每個(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è)地方我們要特別注意一下,deActivatecanActivate的觸發(fā)都是基于route的改變躬络,一般來(lái)說(shuō)就是使用了navigate或者navagateByUrl尖奔,redirect之類(lèi)的方法改變了你的當(dāng)前所在路由。

經(jīng)常我們對(duì)于logout()方法主要做兩件事:

  1. 刪除用戶當(dāng)前的登錄信息 => token for example;
  2. 刷新界面/返回登錄界面

那么我們可以看到洗鸵,當(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é)纯丸。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末偏形,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子液南,更是在濱河造成了極大的恐慌壳猜,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件滑凉,死亡現(xiàn)場(chǎng)離奇詭異统扳,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)畅姊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)咒钟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人若未,你說(shuō)我怎么就攤上這事朱嘴。” “怎么了粗合?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵萍嬉,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我隙疚,道長(zhǎng)壤追,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任供屉,我火速辦了婚禮行冰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘伶丐。我一直安慰自己悼做,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布哗魂。 她就那樣靜靜地躺著肛走,像睡著了一般。 火紅的嫁衣襯著肌膚如雪录别。 梳的紋絲不亂的頭發(fā)上朽色,一...
    開(kāi)封第一講書(shū)人閱讀 52,268評(píng)論 1 309
  • 那天故硅,我揣著相機(jī)與錄音,去河邊找鬼纵搁。 笑死吃衅,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的腾誉。 我是一名探鬼主播徘层,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼利职!你這毒婦竟也來(lái)了趣效?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤猪贪,失蹤者是張志新(化名)和其女友劉穎跷敬,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體热押,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡西傀,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了桶癣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拥褂。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖牙寞,靈堂內(nèi)的尸體忽然破棺而出饺鹃,到底是詐尸還是另有隱情,我是刑警寧澤间雀,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布悔详,位于F島的核電站,受9級(jí)特大地震影響惹挟,放射性物質(zhì)發(fā)生泄漏茄螃。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一匪煌、第九天 我趴在偏房一處隱蔽的房頂上張望责蝠。 院中可真熱鬧党巾,春花似錦萎庭、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至署海,卻和暖如春吗购,著一層夾襖步出監(jiān)牢的瞬間医男,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工捻勉, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留镀梭,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓踱启,卻偏偏與公主長(zhǎng)得像报账,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子埠偿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容