Angular 入坑記錄的筆記第六篇,介紹 Angular 路由模塊中關於路由守衛的相關知識點,瞭解經常使用到的路由守衛接口,知道如何經過實現路由守衛接口來實現特定的功能需求,以及實現對於特性模塊的惰性加載html
對應官方文檔地址:git
配套代碼地址:angular-practice/src/router-combat程序員
重複上一篇筆記的內容,搭建一個包含路由配置的 Angualr 項目github
新建四個組件,分別對應於三個實際使用到的頁面與一個設置爲通配路由的 404 頁面typescript
-- 危機中心頁面
ng g component crisis-list
-- 英雄中心頁面
ng g component hero-list
-- 英雄相親頁面
ng g component hero-detail
-- 404 頁面
ng g component page-not-found
複製代碼
在 app-routing.module.ts 文件中完成對於項目路由的定義,這裏包含了對於路由的重定向、通配路由,以及經過動態路由進行參數傳遞的使用shell
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
// 引入組件
import { CrisisListComponent } from './crisis-list/crisis-list.component';
import { HeroListComponent } from './hero-list/hero-list.component';
import { HeroDetailComponent } from './hero-detail/hero-detail.component';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
const routes: Routes = [
{
path: 'crisis-center',
component: CrisisListComponent,
},
{
path: 'heroes',
component: HeroListComponent,
},
{
path: 'hero/:id',
component: HeroDetailComponent,
},
{
path: '',
redirectTo: '/heroes',
pathMatch: 'full',
},
{
path: '**',
component: PageNotFoundComponent,
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule { }
複製代碼
以後,在根組件中,添加 router-outlet 標籤用來聲明路由在頁面上渲染的出口編程
<h1>Angular Router</h1>
<nav>
<a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a>
<a routerLink="/heroes" routerLinkActive="active">Heroes</a>
</nav>
<router-outlet></router-outlet>
複製代碼
在 Angular 中,路由守衛主要能夠解決如下的問題數組
Angular 路由模塊提供了以下的幾個接口用來幫助咱們解決上面的問題app
在添加了路由守衛以後,經過路由守衛返回的值,從而達到咱們控制路由的目的框架
在實現路由守衛以前,能夠經過 Angular CLI 來生成路由守衛的接口實現類,經過命令行,在 app/auth 路徑下生成一個受權守衛類,CLI 會提示咱們選擇繼承的路由守衛接口,這裏選擇 CanActivate 便可
ng g guard auth/auth
複製代碼
在 AuthGuard 這個路由守衛類中,咱們模擬了是否容許訪問一個路由地址的認證受權。首先判斷是否已經登陸,若是登陸後再判斷當前登陸人是否具備當前路由地址的訪問權限
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from '@angular/router';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
/** * ctor * @param router 路由 */
constructor(private router: Router) { }
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
// 判斷是否有 token 信息
let token = localStorage.getItem('auth-token') || '';
if (token === '') {
this.router.navigate(['/login']);
return false;
}
// 判斷是否能夠訪問當前鏈接
let url: string = state.url;
if (token === 'admin' && url === '/crisis-center') {
return true;
}
this.router.navigate(['/login']);
return false;
}
}
複製代碼
以後咱們就能夠在 app-routing.module.ts 文件中引入 AuthGuard 類,針對須要保護的路由進行路由守衛的配置
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
// 引入組件
import { CrisisListComponent } from './crisis-list/crisis-list.component';
// 引入路由守衛
import { AuthGuard } from './auth/auth.guard';
const routes: Routes = [
{
path: 'crisis-center',
component: CrisisListComponent,
canActivate: [AuthGuard], // 添加針對當前路由的 canActivate 路由守衛
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule { }
複製代碼
與繼承 CanActivate 接口進行路由守衛的方式類似,針對子路由的認證受權能夠經過繼承 CanActivateChild 接口來實現,由於受權的邏輯很類似,這裏經過多重繼承的方式,擴展 AuthGuard 的功能,從而達到同時針對路由和子路由的路由守衛
改造下原先 canActivate 方法的實現,將認證邏輯修改成用戶的 token 信息中包含 admin 便可訪問 crisis-center 頁面,在針對子路由進行認證受權的 canActivateChild 方法中,經過判斷 token 信息是否爲 admin-master 模擬完成對於子路由的訪問認證
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router, CanActivateChild } from '@angular/router';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate, CanActivateChild {
/** * ctor * @param router 路由 */
constructor(private router: Router) { }
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
// 判斷是否有 token 信息
let token = localStorage.getItem('auth-token') || '';
if (token === '') {
this.router.navigate(['/login']);
return false;
}
// 判斷是否能夠訪問當前鏈接
let url: string = state.url;
if (token.indexOf('admin') !== -1 && url.indexOf('/crisis-center') !== -1) {
return true;
}
this.router.navigate(['/login']);
return false;
}
canActivateChild(
childRoute: ActivatedRouteSnapshot,
state: RouterStateSnapshot): boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> {
let token = localStorage.getItem('auth-token') || '';
if (token === '') {
this.router.navigate(['/login']);
return false;
}
return token === 'admin-master';
}
}
複製代碼
經過 Angular CLI 新增一個 crisis-detail 組件,做爲 crisis-list 的子組件
ng g component crisis-detail
複製代碼
接下來在 crisis-list 中添加 router-outlet 標籤,用來定義子路由的渲染出口
<h2>危機中心</h2>
<ul class="crises">
<li *ngFor="let crisis of crisisList">
<a [routerLink]="[crisis.id]">
<span class="badge">{{ crisis.id }}</span>{{ crisis.name }}
</a>
</li>
</ul>
<!-- 定義子路由的渲染出口 -->
<router-outlet></router-outlet>
複製代碼
在針對子路由的認證受權配置時,咱們能夠選擇針對每一個子路由添加 canActivateChild 屬性,也能夠定義一個空地址的子路由,將全部歸屬於 crisis-list 的子路由做爲這個空路由的子路由,經過針對這個空路徑添加 canActivateChild 屬性,從而實現將守護規則應用到全部的子路由上
這裏其實至關於將原先兩級的路由模式(父:crisis-list,子:crisis-detail)改爲了三級(父:crisis-list,子:' '(空路徑),孫:crisis-detail)
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
// 引入組件
import { CrisisListComponent } from './crisis-list/crisis-list.component';
import { CrisisDetailComponent } from './crisis-detail/crisis-detail.component';
// 引入路由守衛
import { AuthGuard } from './auth/auth.guard';
const routes: Routes = [
{
path: 'crisis-center',
component: CrisisListComponent,
canActivate: [AuthGuard], // 添加針對當前路由的 canActivate 路由守衛
children: [{
path: '',
canActivateChild: [AuthGuard], // 添加針對子路由的 canActivate 路由守衛
children: [{
path: 'detail',
component: CrisisDetailComponent
}]
}]
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule { }
複製代碼
當進行表單填報之類的操做時,由於會涉及到一個提交的動做,當用戶沒有點擊保存按鈕就離開時,最好能暫停,對用戶進行一個友好性的提示,由用戶選擇後續的操做
建立一個路由守衛,繼承於 CanDeactivate 接口
ng g guard hero-list/guards/hero-can-deactivate
複製代碼
與上面的 CanActivate、CanActivateChild 路由守衛的使用方式不一樣,對於 CanDeactivate 守衛來講,咱們須要將參數中的 unknown 替換成咱們實際須要進行路由守衛的組件
import { Injectable } from '@angular/core';
import { CanDeactivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class HeroCanDeactivateGuard implements CanDeactivate<unknown> {
canDeactivate(
component: unknown,
currentRoute: ActivatedRouteSnapshot,
currentState: RouterStateSnapshot,
nextState?: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
return true;
}
}
複製代碼
例如,這裏針對的是 HeroListComponent 這個組件,所以咱們須要將泛型的參數 unknown 改成 HeroListComponent,經過 component 參數,就能夠得到須要進行路由守衛的組件的相關信息
import { Injectable } from '@angular/core';
import {
CanDeactivate,
ActivatedRouteSnapshot,
RouterStateSnapshot,
UrlTree,
} from '@angular/router';
import { Observable } from 'rxjs';
// 引入須要進行路由守衛的組件
import { HeroListComponent } from '../hero-list.component';
@Injectable({
providedIn: 'root',
})
export class HeroCanDeactivateGuard
implements CanDeactivate<HeroListComponent> {
canDeactivate(
component: HeroListComponent,
currentRoute: ActivatedRouteSnapshot,
currentState: RouterStateSnapshot,
nextState?: RouterStateSnapshot
):
| Observable<boolean | UrlTree>
| Promise<boolean | UrlTree>
| boolean
| UrlTree {
// 判斷是否修改了原始數據
//
const data = component.hero;
if (data === undefined) {
return true;
}
const origin = component.heroList.find(hero => hero.id === data.id);
if (data.name === origin.name) {
return true;
}
return window.confirm('內容未提交,確認離開?');
}
}
複製代碼
這裏模擬判斷用戶有沒有修改原始的數據,當用戶修改了數據並移動到別的頁面時,觸發路由守衛,提示用戶是否保存後再離開當前頁面
當應用逐漸擴大,使用現有的加載方式會形成應用在第一次訪問時就加載了所有的組件,從而致使系統首次渲染過慢。所以這裏可使用惰性加載的方式在請求具體的模塊時才加載對應的組件
惰性加載只針對於特性模塊(NgModule),所以爲了使用惰性加載這個功能點,咱們須要將系統按照功能劃分,拆分出一個個獨立的模塊
首先經過 Angular CLI 建立一個危機中心模塊(crisis 模塊)
-- 查看建立模塊的相關參數
ng g module --help
-- 建立危機中心模塊(自動在 app.moudule.ts 中引入新建立的 CrisisModule、添加當前模塊的路由配置)
ng g module crisis --module app --routing
複製代碼
將 crisis-list、crisis-detail 組件所有移動到 crisis 模塊下面,並在 CrisisModule 中添加對於 crisis-list、crisis-detail 組件的聲明,同時將原來在 app.module.ts 中聲明的組件代碼移除
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CrisisRoutingModule } from './crisis-routing.module';
import { FormsModule } from '@angular/forms';
// 引入模塊中使用到的組件
import { CrisisListComponent } from './crisis-list/crisis-list.component';
import { CrisisDetailComponent } from './crisis-detail/crisis-detail.component';
@NgModule({
declarations: [
CrisisListComponent,
CrisisDetailComponent
],
imports: [
CommonModule,
FormsModule,
CrisisRoutingModule
]
})
export class CrisisModule { }
複製代碼
一樣的,將當前模塊的路由配置移動到專門的路由配置文件 crisis-routing.module.ts 中,並將 app-routing.module.ts 中相關的路由配置刪除
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
// 引入組件
import { CrisisListComponent } from './crisis-list/crisis-list.component';
import { CrisisDetailComponent } from './crisis-detail/crisis-detail.component';
// 引入路由守衛
import { AuthGuard } from '../auth/auth.guard';
const routes: Routes = [{
path: '',
component: CrisisListComponent,
canActivate: [AuthGuard], // 添加針對當前路由的 canActivate 路由守衛
children: [{
path: '',
canActivateChild: [AuthGuard], // 添加針對子路由的 canActivate 路由守衛
children: [{
path: 'detail',
component: CrisisDetailComponent
}]
}]
}];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class CrisisRoutingModule { }
複製代碼
從新運行項目,若是你在建立模塊的命令中設置了自動引入當前模塊到 app.module.ts 文件中,大機率會遇到下面的問題
這裏的問題與配置通配路由須要放到最後的緣由類似,由於腳手架在幫咱們將建立的模塊導入到 app.module.ts 中時,是添加到整個數組的最後,同時由於咱們已經將 crisis 模塊的路由配置移動到專門的 crisis-routing.module.ts 中了,框架在進行路由匹配時會預先匹配上 app-routing.module.ts 中設置的通配路由,從而致使沒法找到實際應該對應的組件,所以這裏咱們須要將 AppRoutingModule 放到聲明的最後
當問題解決後,就能夠針對 crisis 模塊設置惰性加載
在配置惰性路由時,咱們須要以一種相似於子路由的方式進行配置,經過路由的 loadChildren 屬性來加載對應的模塊,而不是具體的組件,修改後的 AppRoutingModule 代碼以下
import { HeroCanDeactivateGuard } from './hero-list/guards/hero-can-deactivate.guard';
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [
{
path: 'crisis-center',
loadChildren: () => import('./crisis/crisis.module').then(m => m.CrisisModule)
}
];
@NgModule({
imports: [RouterModule.forRoot(routes, { enableTracing: true })],
exports: [RouterModule],
})
export class AppRoutingModule { }
複製代碼
當導航到這個 /crisis-center 路由時,框架會經過 loadChildren 字符串來動態加載 CrisisModule,而後把 CrisisModule 添加到當前的路由配置中,而惰性加載和從新配置工做只會發生一次,也就是在該路由首次被請求時執行,在後續請求時,該模塊和路由都是當即可用的
在上面的代碼中,對於 CrisisModule 模塊咱們已經使用 CanActivate、CanActivateChild 路由守衛來進行路由的認證受權,可是當咱們並無權限訪問該路由的權限,卻依然點擊了連接時,此時框架路由仍會加載該模塊。爲了杜絕這種受權未經過仍加載模塊的問題發生,這裏須要使用到 CanLoad 守衛
由於這裏的判斷邏輯與認證受權的邏輯相同,所以在 AuthGuard 中,繼承 CanLoad 接口便可,修改後的 AuthGuard 代碼以下
import { Injectable } from '@angular/core';
import {
CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router, CanActivateChild, CanLoad, Route, UrlSegment
} from '@angular/router';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate, CanActivateChild, CanLoad {
/** * ctor * @param router 路由 */
constructor(private router: Router) { }
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
// 判斷是否有 token 信息
let token = localStorage.getItem('auth-token') || '';
if (token === '') {
this.router.navigate(['/login']);
return false;
}
// 判斷是否能夠訪問當前鏈接
let url: string = state.url;
if (token.indexOf('admin') !== -1 && url.indexOf('/crisis-center') !== -1) {
return true;
}
this.router.navigate(['/login']);
return false;
}
canActivateChild(
childRoute: ActivatedRouteSnapshot,
state: RouterStateSnapshot): boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> {
let token = localStorage.getItem('auth-token') || '';
if (token === '') {
this.router.navigate(['/login']);
return false;
}
return token === 'admin-master';
}
canLoad(route: Route, segments: UrlSegment[]): boolean | Observable<boolean> | Promise<boolean> {
let token = localStorage.getItem('auth-token') || '';
if (token === '') {
this.router.navigate(['/login']);
return false;
}
let url = `/${route.path}`;
if (token.indexOf('admin') !== -1 && url.indexOf('/crisis-center') !== -1) {
return true;
}
}
}
複製代碼
一樣的,針對路由守衛的實現完成後,將須要使用到的路由守衛添加到 crisis-center 路由的 canLoad 數組中便可實現受權認證不經過時不加載模塊
import { HeroCanDeactivateGuard } from './hero-list/guards/hero-can-deactivate.guard';
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [
{
path: 'crisis-center',
loadChildren: () => import('./crisis/crisis.module').then(m => m.CrisisModule)
}
];
@NgModule({
imports: [RouterModule.forRoot(routes, { enableTracing: true })],
exports: [RouterModule],
})
export class AppRoutingModule { }
複製代碼
佔坑
做者:墨墨墨墨小宇
我的簡介:96年生人,出生於安徽某四線城市,畢業於Top 10000000 院校。.NET程序員,槍手死忠,喵星人。於2016年12月開始.NET程序員生涯,微軟.NET技術的堅決堅持者,立志成爲雲養貓的少年中面向谷歌編程最厲害的.NET程序員。
我的博客:yuiter.com
博客園博客:www.cnblogs.com/danvic712