相信大家都用使用過 Facebook 吧?
當我們想在 Facebook 發文或留言的時候,如果不小心按到上一頁/下一頁,抑或是沒有取消發文或留言的狀態就想離開頁面的時候,應該會看到類似這樣子的訊息:
既然我們昨天可以在使用者想要造訪某個路由時,透過 Guard 來替我們的路由把關,那是不是也可以像 Facebook 一樣,在使用者想要離開某個路由時,一樣透過 Guard 來幫我提醒使用者呢?
當然可以!而且使用方式大致上跟昨天很像,我們來快速複習一下。
假設我們今天要在 Login 頁面設上述這種類型的守門員的話,我們一樣可以先使用 Angular CLI 建立一個 Guard :
ng generate guard login/ensure-login
然後打開 ensure-login.guard.ts
,應該也會是長這樣:
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class EnsureLoginGuard implements CanActivate {
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
return true;
}
到目前為止都是跟昨天一樣,不過我們今天要做的事情跟昨天不一樣,所以需要調整一下程式碼。
首先我們先把 canActivate
函式整個移除,然後將 CanActivate 介面改為 CanDeactivate:
export class EnsureLoginGuard implements CanDeactivate<LoginComponent> {
}
CanDeactivate 這個介面有泛型,泛型的型別則是這個 Guard 要我們處理那個路由所設定的 Component 的型別。像我們今天就是要讓它來幫我們處理 LoginComponent 這個 Component 。
當我們的程式碼改成這樣的時候,應該會看到 EnsureLoginGuard 有紅色波浪底線出現:
滑鼠游標移過去之後,VSCode 會這樣告訴你:
這個意思是 CanDeactivate 需要實作 canDeactivate
這個函式,所以我們其實只要加上去就好了。
不過今天要教大家一個小技巧 (其實早就該教了XD) ,這時候我們只要點選 EnsureLoginGuard 或是將游標停在上面之後,按下 ctrl + '.'
(masOS 則是按下 command + '.'
) , VSCode 就會幫我們把函式加進去:
但我覺得 VSCode 幫我們處理得沒那麼好,我們調整一下好了:
export class EnsureLoginGuard implements CanDeactivate<LoginComponent> {
/**
* 當使用者要離開這個 Guard 所防守的路由時,會觸發這個函式
*
* @param {LoginComponent} component - 該路由的 Component
* @param {ActivatedRouteSnapshot} currentRoute - 當前的路由
* @param {RouterStateSnapshot} currentState - 當前路由狀態的快照
* @param {RouterStateSnapshot} [nextState] - 欲前往路由的路由狀態的快照
* @returns {(boolean | Observable<boolean> | Promise<boolean>)}
* @memberof EnsureLoginGuard
*/
canDeactivate(
component: LoginComponent,
currentRoute: ActivatedRouteSnapshot,
currentState: RouterStateSnapshot,
nextState?: RouterStateSnapshot
): boolean | Observable<boolean> | Promise<boolean> {
return false;
}
}
然後我們再打開 app-routing.module.ts
這個檔案,把 EnsureLoginGuard 加到 LoginComponent 的路由裡:
{
path: 'login',
component: LoginComponent,
canDeactivate: [EnsureLoginGuard]
}
有注意到嗎?昨天我們用的是
canActivate
,今天則是改用canDeactivate
。
加完之後我們來看看畫面:
糟糕!怎麼都沒辦法登入了?!是不是壞了?!
別擔心,這其實是因為我們在 EnsureLoginGuard 的 canDeactivate
函式裡回傳 false
的關係。表示我們不讓使用者離開那個頁面的意思。
我們再來調整一下程式碼,讓使用者要離開該頁面的時候跳個提示:
canDeactivate(
component: LoginComponent,
currentRoute: ActivatedRouteSnapshot,
currentState: RouterStateSnapshot,
nextState?: RouterStateSnapshot
): boolean | Observable<boolean> | Promise<boolean> {
return confirm('是否要離開此頁面?');
}
然後再來看一下畫面:
如此一來我們就可以依照使用者自己的意願來決定要不要導頁了。
不過在實務上應用時,通常判斷的邏輯會會再稍微複雜一點點。
舉例來說,假設今天有個情境是:使用者正在打文章或是表單,如果使用者打到一半的時候要離開這個頁面的話,就跳提示訊息讓使用者自己判斷要不要離開;如果使用者都還沒有任何輸入的時候要離開就直接離開。
這時候我們就必須判斷使用者有沒有任何的輸入,再決定要不要跳提示訊息了。
怎麼做呢?
我們可以先在 LoginComponent 的 Template 裡加上 input 的標籤:
<input type="text" [(ngModel)]="name">
因為我們有用 [(ngModel)]
的關係,所以要引入 FromsModule 到我們的 AppModule 裡:
import { FormsModule } from '@angular/forms';
@NgModule({
declarations: [
// ...
],
imports: [
BrowserModule,
AppRoutingModule,
FormsModule // 加到這裡
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
然後我們雙向綁定了 name
這個屬性,所以到 LoginComponent 裡新增一下這個屬性:
name = '';
最後調整一下 EnsureLoginGuard 的程式碼:
canDeactivate(
component: LoginComponent,
currentRoute: ActivatedRouteSnapshot,
currentState: RouterStateSnapshot,
nextState?: RouterStateSnapshot
): boolean | Observable<boolean> | Promise<boolean> {
if (component.name.trim()) {
return confirm('是否要離開此頁面?');
}
return true;
}
來看一下改完的效果:
好的!如此一來就完成了第二種路由守門員了!!
是不是超簡單又超輕鬆的阿?!
這七天的範例程式碼我會放在 GitHub 上,有興趣的邦友們都可以自由下載與運用噢!
經過了漫長的七天之後,關於路由的部份也終於可以告一個段落了。
明天會幫大家總結一下這七天所學到的東西,敬請期待!