## 一、路由守卫:当用户满足一定条件才被允许进入或者离开一个路由。
>[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
- 包体优化