## 一、路由守衛:當用戶滿足一定條件才被允許進入或者離開一個路由。
>[warning] 路由守衛場景:
* 該用戶可能無權導航到目標組件。
* 可能用戶得先登錄(認證)。
* 在顯示目標組件前,你可能得先獲取某些數據。
* 在離開組件前,你可能要先保存修改。
* 你可能要詢問用戶:你是否要放棄本次更改,而不用保存它們?
>[info] 路由守衛返回一個布爾值以控制路由:
* 如果它返回`true`,導航過程會繼續
* 如果它返回`false`,導航過程就會終止,且用戶留在原地。
* 如果它返回`UrlTree`,則取消當前的導航,并且開始導航到返回的這個`UrlTree`
>[danger] 守衛可以用同步的方式返回一個布爾值,但大多情況都是異步的,例如網絡請求,因此路由守衛可以返回一個Observable對象 或 Promise對象,并且路由器會等待這個可觀察對象被解析為`true`或`false`。
## 二、路由守衛接口
>[info] 路由器可以支持多種守衛接口:
* 用`CanActivate`來處理導航到某路由的情況。
* 用`CanActivateChild`來處理導航到某子路由的情況。
* 用`CanDeactivate`來處理從當前路由離開的情況.
* 用`Resolve`在路由激活之前獲取路由數據。
* 用`CanLoad`來處理異步導航到某特性模塊的情況。
> 路由器會先按照從最深的子路由由下往上檢查的順序來檢查 CanDeactivate() 和 CanActivateChild() 守衛。 然后它會按照從上到下的順序檢查 CanActivate() 守衛。如果特性模塊是異步加載的,在加載它之前還會檢查 CanLoad() 守衛。 如果任何一個守衛返回 false,其它尚未完成的守衛會被取消,這樣整個導航就被取消了。
### 1.CanActivate:要求認證(登錄認證、權限認證)
創建一個admin特征模塊:`ng generate module admin --routing`
創建admin特征模塊的子組件:
* `ng generate component admin/admin-dashboard`
* `content_copyng generate component admin/admin`
* `content_copyng generate component admin/manage-crises`
* `content_copyng generate component admin/manage-heroes`
admin特征模塊的文件內容如下:
```
src/app/admin
├─admin
│ ├─admin.component.css
│ ├─admin.component.html
│ └─admin.component.ts
├─admin-dashboard
│ ├─admin-dashboard.component.css
│ ├─admin-dashboard.component.html
│ └─admin-dashboard.component.ts
├─manage-crises
│ ├─manage-crises.component.css
│ ├─manage-crises.component.html
│ └─manage-crises.component.ts
├─manage-heroes
│ ├─manage-heroes.component.css
│ ├─manage-heroes.component.html
│ └─manage-heroes.component.ts
├─admin.module.ts
└─admin-routing.module.ts
```
>[info] src/app/admin/admin/admin.component.html文件內容
```
<h3>ADMIN</h3>
<nav>
<a routerLink="./" routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }">Dashboard</a>
<a routerLink="./crises" routerLinkActive="active">Manage Crises</a>
<a routerLink="./heroes" routerLinkActive="active">Manage Heroes</a>
</nav>
<router-outlet></router-outlet>
```
> 路由鏈接中有一個斜杠/的路由,表示它能夠匹配admin特征模塊中的任何路由,但是只希望在訪問 Dashboard 路由時才激活該鏈接,往Dashboard 這個 routerLink 上添加另一個綁定 \[routerLinkActiveOptions\]="{ exact: true }",這樣導航到admin特征模塊時會激活Dashboard 路由而不會激活其他路由 (訪問admin特征模塊時激活Dashboard 路由)
#### 無組件路由: 分組路由,而不需要組件
>[info] admin特征模塊下的路由配置文件:
```
const adminRoutes: Routes = [
{
path: 'admin',
component: AdminComponent,
children: [
{
path: '',
children: [
{ path: 'crises', component: ManageCrisesComponent },
{ path: 'heroes', component: ManageHeroesComponent },
{ path: '', component: AdminDashboardComponent }
]
}
]
}
];
@NgModule({
imports: [
RouterModule.forChild(adminRoutes)
],
exports: [
RouterModule
]
})
export class AdminRoutingModule {}
```
> `AdminComponent`的子路由,有**path**屬性和**children**屬性, 但它沒有使用**component**。代表這是一個無組件路由
> 這里是對`admin`路徑下的`危機中心`管理類路由進行分組,并不需要另一個僅用來分組路由的組件。 一個無組件的路由能讓守衛子路由變得更容易。
在app.module.ts中導入AdminModule并把它加入`imports`數組中來注冊這些管理類路由。
在app.component.html模板中添加admin特征模塊的導航鏈接
```
<h1 class="title">Angular Router</h1>
<nav>
<a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a>
<a routerLink="/heroes" routerLinkActive="active">Heroes</a>
<a routerLink="/admin" routerLinkActive="active">Admin</a>
<a [routerLink]="[{ outlets: { popup: ['compose'] } }]">Contact</a>
</nav>
<div [@routeAnimation]="getAnimationData(routerOutlet)">
<router-outlet #routerOutlet="outlet"></router-outlet>
</div>
<router-outlet name="popup"></router-outlet>
```
#### 使用CanActivate接口守衛admin特征模塊
>[danger] 目前admin特征模塊的路由對所有人都是開放的,這顯然不夠安全,使用CanActivate() 守衛,將正在嘗試訪問admin特征模塊的匿名用戶重定向到登錄頁,只有登錄的用戶才有權訪問
這是一個通用的守衛(假設另外一些特征模塊也要求已認證過的用戶才能訪問),所以可以在`auth`目錄下生成一個`AuthGuard`。
一、創建路由守衛
>[info] 在CLI輸入命令:ng generate guard auth/auth(路由守衛類名)
輸入命令后創建的路由守衛`src/app/auth/auth.guard.ts`
```
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
@Injectable({
providedIn: 'root',
})
export class AuthGuard implements CanActivate {
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): boolean {
console.log('AuthGuard#canActivate called');
return true;
}
}
```
二、添加路由守衛
在`admin`特征模塊路由文件中引入`AuthGuard`類并添加`CanActivate()` 守衛
>[danger] canActivate可以指定多個守衛,值是一個數組。
```
//引入路由守衛類
import { AuthGuard } from '../auth/auth.guard';
const adminRoutes: Routes = [
{
path: 'admin',
component: AdminComponent,
canActivate: [AuthGuard], //添加canActivate路由守衛
children: [
{
path: '',
children: [
{ path: 'crises', component: ManageCrisesComponent },
{ path: 'heroes', component: ManageHeroesComponent },
{ path: '', component: AdminDashboardComponent }
],
}
]
}
];
@NgModule({
imports: [
RouterModule.forChild(adminRoutes)
],
exports: [
RouterModule
]
})
export class AdminRoutingModule {}
```
三、編寫路由守衛邏輯進行認證
創建一個服務進行授權認證,該服務能讓用戶登錄(模擬登陸),并且保存當前用戶的信息
>[info] 在CLI輸入命令:ng generate service auth/auth
輸入命令后創建的服務`src/app/auth/auth.service.ts`
```
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { tap, delay } from 'rxjs/operators';
@Injectable({
providedIn: 'root',
})
export class AuthService {
isLoggedIn = false;
// 存儲url,用于登錄后進行跳轉
redirectUrl: string;
login(): Observable<boolean> {
return of(true).pipe(
delay(1000),
tap(val => this.isLoggedIn = true)
);
}
logout(): void {
this.isLoggedIn = false;
}
}
```
在路由守衛中使用服務進行登錄驗證
```
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { AuthService } from './auth.service';
@Injectable({
providedIn: 'root',
})
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(
next: ActivatedRouteSnapshot, // ActivatedRouteSnapshot 包含了即將被激活的路由
state: RouterStateSnapshot): boolean { //RouterStateSnapshot 包含了該應用即將到達的狀態
let url: string = state.url;
return this.checkLogin(url);
}
checkLogin(url: string): boolean {
if (this.authService.isLoggedIn) { return true; }
//存儲要跳轉到的url
this.authService.redirectUrl = url;
// 跳轉到登錄
this.router.navigate(['/login']);
return false;
}
}
```
>[info] 創建一個登錄組件用于用戶登錄,登錄成功后將導航到原來的url。
### 2.CanActivateChild:保護子路由
>[info] 使用CanActivateChild 路由守衛來保護子路由。 CanActivateChild 守衛和 CanActivate 守衛很像。 區別在于,CanActivateChild 會在任何子路由被激活之前運行。
一、擴展`AuthGuard`,添加`CanActivateChild `接口。
```
import { Injectable } from '@angular/core';
import {
CanActivate, Router,
ActivatedRouteSnapshot,
RouterStateSnapshot,
CanActivateChild //添加CanActivateChild接口
} from '@angular/router';
import { AuthService } from './auth.service';
@Injectable({
providedIn: 'root',
})
export class AuthGuard implements CanActivate, CanActivateChild { //繼承CanActivateChild 接口
constructor(private authService: AuthService, private router: Router) {}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): boolean {
let url: string = state.url;
return this.checkLogin(url);
}
//實現canActivateChild路由邏輯
canActivateChild(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): boolean {
return this.canActivate(route, state);
}
/* . . . */
}
```
二、在特征路由中添加`canActivateChild`路由守衛
同樣把這個`AuthGuard`添加到“無組件的”管理路由,來同時保護它的所有子路由,而不是為每個路由單獨添加這個`AuthGuard`。
```
const adminRoutes: Routes = [
{
path: 'admin',
component: AdminComponent,
canActivate: [AuthGuard],
children: [
{
path: '',
canActivateChild: [AuthGuard], //添加canActivateChild路由守衛
children: [
{ path: 'crises', component: ManageCrisesComponent },
{ path: 'heroes', component: ManageHeroesComponent },
{ path: '', component: AdminDashboardComponent }
]
}
]
}
];
@NgModule({
imports: [
RouterModule.forChild(adminRoutes)
],
exports: [
RouterModule
]
})
export class AdminRoutingModule {}
```
### 3.CanDeactivate:處理未保存的更改
一、創建一個守衛
>[info] 在CLI輸入命令:ng generate guard can-deactivate
輸入命令后創建的文件`src/app/can-deactivate.guard.ts`
```
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class CanDeactivatesGuard implements CanActivate {
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
return true;
}
}
```
二、實現CanDeactivate路由守衛邏輯
>[info]實現CanDeactivate路由守衛的邏輯,判斷表單的數據是否有改變,當數據有所改變用戶卻要離開頁面時提示用戶數據未保存
1.單獨為組件實現CanDeactivate守衛
```
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { CanDeactivate,
ActivatedRouteSnapshot,
RouterStateSnapshot } from '@angular/router';
import { CrisisDetailComponent } from './crisis-center/crisis-detail/crisis-detail.component';
@Injectable({
providedIn: 'root',
})
export class CanDeactivateGuard implements CanDeactivate<CrisisDetailComponent> {
canDeactivate(
component: CrisisDetailComponent, //組件
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean> | boolean {
// 獲取危機中心的id
console.log(route.paramMap.get('id'));
//獲取當前url
console.log(state.url);
// 如果表單的值沒有改變,則繼續導航
if (!component.crisis || component.crisis.name === component.editName) {
return true;
}
//彈出確認框
return component.dialogService.confirm('Discard changes?');
}
}
```
2.實現復用的CanDeactivate守衛
(1)在組件中實現CanDeactivate方法
```
export class CrisisDetailComponent implements OnInit {
crisis: Crisis;
editName: string;
constructor(
private route: ActivatedRoute,
private router: Router,
public dialogService: DialogService
) {}
//在組件中實現canDeactivate路由守衛的實現邏輯
canDeactivate(): Observable<boolean> | boolean {
// 如果表單的值沒有改變,則繼續導航
if (!component.crisis || component.crisis.name === component.editName) {
return true;
}
//彈出確認框
return component.dialogService.confirm('Discard changes?');
}
}
```
(2)在CanDeactivateGuard中調用組件的CanDeactivate方法
```
import { Injectable } from '@angular/core';
import { CanDeactivate } from '@angular/router';
import { Observable } from 'rxjs';
export interface CanComponentDeactivate {
canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean;
}
@Injectable({
providedIn: 'root',
})
export class CanDeactivateGuard implements CanDeactivate<CanComponentDeactivate> {
canDeactivate(component: CanComponentDeactivate) {
//組件中是否有canDeactivate方法,有就執行沒有返回true
return component.canDeactivate ? component.canDeactivate() : true;
}
}
```
三、在路由配置文件中添加CanDeactivate守衛
```
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CrisisCenterHomeComponent } from './crisis-center-home/crisis-center-home.component';
import { CrisisListComponent } from './crisis-list/crisis-list.component';
import { CrisisCenterComponent } from './crisis-center/crisis-center.component';
import { CrisisDetailComponent } from './crisis-detail/crisis-detail.component';
import { CanDeactivateGuard } from '../can-deactivate.guard';
import { CrisisDetailResolverService } from './crisis-detail-resolver.service';
const crisisCenterRoutes: Routes = [
{
path: '',
component: CrisisCenterComponent,
children: [
{
path: '',
component: CrisisListComponent,
children: [
{
path: ':id',
component: CrisisDetailComponent,
canDeactivate: [CanDeactivateGuard],//添加canDeactivate路由守衛
resolve: {
crisis: CrisisDetailResolverService
}
},
{
path: '',
component: CrisisCenterHomeComponent
}
]
}
]
}
];
@NgModule({
imports: [
RouterModule.forChild(crisisCenterRoutes)
],
exports: [
RouterModule
]
})
export class CrisisCenterRoutingModule { }
```
### 4.Resolve: 預先獲取組件數據
>[warning] resolve:在進入路由之前去服務器讀數據,把需要的數據都讀好以后,帶著這些數據進到路由里,立刻就把數據顯示出來。
一、創建服務獲取數據
>[info] 在CLI輸入命令:ng generate service crisis-center/crisis-detail-resolver
輸入命令后創建的服務`src/app/crisis-center/crisis-detail-resolver.service.ts`
```
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class CrisisDetailResolverService {
constructor() { }
}
```
>[info] 使用服務獲取數據:
```
import { Injectable } from '@angular/core';
import {
Router, Resolve,
RouterStateSnapshot,
ActivatedRouteSnapshot
} from '@angular/router';
import { Observable, of, EMPTY } from 'rxjs';
import { mergeMap, take } from 'rxjs/operators';
import { CrisisService } from './crisis.service';
import { Crisis } from './crisis';
@Injectable({
providedIn: 'root',
})
export class CrisisDetailResolverService implements Resolve<Crisis> {
constructor(private cs: CrisisService, private router: Router) {}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Crisis> | Observable<never> {
let id = route.paramMap.get('id'); //獲取路由傳遞的參數
//獲取數據,通常會發起網絡請求獲取數據,此處只是模擬
return this.cs.getCrisis(id).pipe(
take(1),
mergeMap(crisis => {
if (crisis) {
return of(crisis);
} else { //id not found
this.router.navigate(['/crisis-center']);
return EMPTY;
}
})
);
}
}
```
二、添加Resolve路由守衛
```
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CrisisCenterHomeComponent } from './crisis-center-home/crisis-center-home.component';
import { CrisisListComponent } from './crisis-list/crisis-list.component';
import { CrisisCenterComponent } from './crisis-center/crisis-center.component';
import { CrisisDetailComponent } from './crisis-detail/crisis-detail.component';
import { CanDeactivateGuard } from '../can-deactivate.guard';
import { CrisisDetailResolverService } from './crisis-detail-resolver.service';
const crisisCenterRoutes: Routes = [
{
path: 'crisis-center',
component: CrisisCenterComponent,
children: [
{
path: '',
component: CrisisListComponent,
children: [
{
path: ':id',
component: CrisisDetailComponent,
canDeactivate: [CanDeactivateGuard],
resolve: {
crisis: CrisisDetailResolverService //添加resolve路由守衛,CrisisDetailResolverService是獲取數據的服務
}
},
{
path: '',
component: CrisisCenterHomeComponent
}
]
}
]
}
];
@NgModule({
imports: [
RouterModule.forChild(crisisCenterRoutes)
],
exports: [
RouterModule
]
})
export class CrisisCenterRoutingModule { }
```
三、組件接收數據
```
ngOnInit() {
this.route.data
.subscribe((data: { crisis: Crisis }) => {
this.editName = data.crisis.name;
this.crisis = data.crisis;
});
}
```
### 5.CanLoad接口:保護對特性模塊的未授權加載
>[info] 目前已使用CanActivate路由守衛保護admin特征模塊,它會阻止未授權的用戶訪問admin模塊,。如果用戶未登錄,它會跳轉到登錄頁。但是admin模塊仍被加載了,因此需要使用CanLoad路由守衛接口實現已登錄的情況才加載admin模塊
一、實現canLoad路由守衛
打開` auth.guard.ts`,從 `@angular/router` 中導入` CanLoad `接口。 把它添加到 AuthGuard 類的 implements 列表中。 然后實現 canLoad,代碼如下:
> 路由器會把 canLoad() 方法的 route 參數設置為準備訪問的目標 URL。 如果用戶已經登錄了,checkLogin() 方法就會重定向到那個 URL。
```
canLoad(route: Route): boolean {
let url = `${route.path}`; //url
return this.checkLogin(url);
}
```
二、添加canLoad路由守衛
```
{
path: 'admin',
loadChildren: './admin/admin.module#AdminModule',
canLoad: [AuthGuard]
},
```
- 目錄結構
- 架構
- 指令
- 數據綁定
- 結構性指令
- 屬性型指令
- 自定義指令
- 模板引用變量
- 屬性綁定
- 事件綁定
- 組件
- 組件交互
- 管道
- 自定義管道
- 動態組件
- 變量檢測機制
- 組件生命周期
- 路由
- 路由配置
- 路由導航
- 路由傳值
- 父子路由
- 路由事件
- 頂級路由和特征路由
- 多重路由
- 路由守衛
- 路由守衛-簡單理解
- 路由惰性加載
- 路由預加載
- 路由動畫
- 網絡請求
- GET請求
- POST請求
- JSOP請求
- 封裝的http請求
- http攔截器
- 表單
- 響應式表單
- 驅動式表單
- CLI命令
- 啟動應用
- 創建項目
- 創建組件
- 創建服務
- 創建路由守衛
- 創建特征模塊
- 創建自定義指令
- 創建自定義管道
- 相關概念
- 急性加載
- 惰性加載
- 特征模塊
- 常見問題
- 全局的Angular CLI大于本地的Angular CLI
- 包體優化