知向前端
angular 中的路由守卫CanActivate、CanDeactivate、Resolve
2019-2-26 Jon
一、路由守卫

当用户满足一定条件才被允许进入或者离开一个路由。

路由守卫场景:

1、某些路由用户未登录无法进入(登录)

2、某些路由用户没权限无法进入(权限)

3、不可跳过中间步骤直接访问某一步骤路由(注册流程、步骤条)

4、当用户未执行保存操作而试图离开当前导航时提醒用户(防止误操作)

5、用户进入某路由前获取该路由需要的数据(提升用户体验)



Angular提供了一些钩子帮助控制进入或离开路由。这些钩子就是路由守卫,可以通过这些钩子实现上面场景。

CanActivate: 是否允许进入某路由(场景1、2、3)

CanDeactivate: 是否允许离开某路由(场景4)

Resolve: 在进入该路由前获取该路由需要的数据(场景5)

配置路由时候用到一些属性,path, component, outlet, children, 路由守卫也是路由属性。



二、CanActivate

实例1 只让登录用户进入产品信息路由。

假设用户登录后会在sessionStorage中存储isLogin为true

1、新建guard目录,目录下新建login-guard.service.ts。

LoginGuard类实现CanActivate接口,返回true或false,Angular根据返回值判断请求通过或不通过。

import { Injectable } from '@angular/core';
import { Router, CanActivate } from '@angular/router';
@Injectable({providedIn: 'root'})
export class LoginGuard implements CanActivate {
constructor(
private router: Router
) {}
canActivate() {
// 这里判断登录状态, 返回 true 或 false
if(sessionStorage['isLogin']) {
return true;
}else {
this.router.navigate(['login']);
return false;
}
}
}

在路由模块中使用LoginGuard 

canActivate可以指定多个守卫,值是一个数组。

// 路由守卫,未登录跳转到登录页
import { LoginGuard } from './guard/login-guard.service';
const routes: Routes = [
{ path: '', redirectTo : 'login',pathMatch:'full' },
{ path: 'login', component: LoginComponent },
{ path: 'home', component: HomeComponent, canActivate: [LoginGuard]},
{ path: 'personal', component: PersonalComponent, canActivate: [LoginGuard]},
{ path: 'account', component: AccountComponent, canActivate: [LoginGuard]},
{ path: 'product', component: ProductComponent, children:[
{ path: '', component : ProductDescComponent },
{ path: 'seller/:id', component : SellerInfoComponent }
] ,canActivate: [LoginGuard]},
{ path: '**', component: Code404Component }
];

现在我们未登录是进入其他路由都会直接跳到登录页面,登录之后我们在跳到其它路由可以正常进入。

下面我们再加几个权限,产品页面权限JViewProduct和账户页面权限JViewAccount

分别控制用户是否可以进入产品详情'product'路由和账户路由'account'

我们登录后拿到的权限存储在localStorage中

在guard目录下新建auth-guard.service.ts文件

import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot } from '@angular/router';
@Injectable({providedIn: 'root'})
export class AuthGuard implements CanActivate {
constructor(
private router: Router
) {}
// 产品页面权限
private JViewProduct = localStorage['JViewProduct']?true:false;
// 账户页面权限
private JViewAccount = localStorage['JViewAccount']?true:false;
canActivate(
route: ActivatedRouteSnapshot
): boolean {
let isPass = true;
if(route.data&&route.data.target) {
switch (route.data.target) {
case 'product':
isPass = this.JViewProduct;
break;
case 'account':
isPass = this.JViewAccount;
break;
default:
break;
}
}
if(!isPass) {
// 如果路由无法访问
this.shouldToPath();
}
return isPass;
}
// 应该跳转到的路径
shouldToPath() {
his.router.navigate(['home']);
}
}

在路由模块中加入AuthGuard

import { LoginGuard } from './guard/login-guard.service';
import { AuthGuard } from './guard/auth-guard.service';
const routes: Routes = [
{ path: '', redirectTo : 'login',pathMatch:'full' },
{ path: 'login', component: LoginComponent },
{ path: 'home', component: HomeComponent, canActivate: [LoginGuard]},
{ path: 'personal', component: PersonalComponent, canActivate: [LoginGuard]},
{ path: 'account', component: AccountComponent, canActivate: [LoginGuard, AuthGuard]},
{ path: 'product', component: ProductComponent, children:[
{ path: '', component : ProductDescComponent },
{ path: 'seller/:id', component : SellerInfoComponent }
] ,canActivate: [LoginGuard, AuthGuard},
{ path: '**', component: Code404Component }
];

这样当用户进入产品或者账户中时首先判断是否登录,没登录直接跳转登录页,若是登录了,再判断是否有权限进入,没有权限则跳转首页,有权限则进入该路由。



三、CanDeactivate

离开时候的路由守卫,提醒用户执行保存操作后才能离开。 

在guard目录下新建一个unsave-guard.service.ts的文件。

CanDeactivate接口有一个范型,指定当前组件的类型。

CanDeactivate方法第一个参数就是接口指定的范型类型的组件,根据这个要保护的组件的状态,或者调用方法来决定用户是否能够离开。




import { CanDeactivate } from "@angular/router";
import { ProductComponent } from "../product/product.component";
@Injectable({providedIn: 'root'})
export class UnsaveGuard implements CanDeactivate<ProductComponent>{
//第一个参数 范型类型的组件
//根据当前要保护组件 的状态 判断当前用户是否能够离开
canDeactivate(component: ProductComponent){
return window.confirm('你还没有保存,确定要离开吗?');
}
}


配置路由,同样先加到provider,再配置路由。

import { LoginGuard } from './guard/login-guard.service';
import { AuthGuard } from './guard/auth-guard.service';
import { UnsaveGuard } from './guard/unsave-guard.service';
const routes: Routes = [
{ path: '', redirectTo : 'login',pathMatch:'full' },
{ path: 'login', component: LoginComponent },
{ path: 'home', component: HomeComponent, canActivate: [LoginGuard]},
{ path: 'personal', component: PersonalComponent, canActivate: [LoginGuard]},
{ path: 'account', component: AccountComponent, canActivate: [LoginGuard, AuthGuard]},
{ path: 'product', component: ProductComponent, children:[
{ path: '', component : ProductDescComponent },
{ path: 'seller/:id', component : SellerInfoComponent }
] ,canActivate: [LoginGuard, AuthGuard], canDeactivate: [UnsaveGuard]},
{ path: '**', component: Code404Component }
];

当从产品路由product离开时点ok离开当前页面,cancel留在当前页面。



四、Resolve守卫

http请求数据返回有延迟,导致模版无法立刻显示。

数据返回之前模版上所有需要用插值表达式显示某个controller的值的地方都是空的。用户体验不好。

resolve解决办法:在进入路由之前去服务器读数据,把需要的数据都读好以后,带着这些数据进到路由里,立刻就把数据显示出来。

实例:

在进入商品信息路由之前,准备好商品信息再进入路由。 拿不到信息,或者拿信息出问题了,直接跳到错误信息页面,或者弹出提示,就不再进入目标路由。

先在product.component.ts中声明商品信息类型。

export class Product{
constructor(public id:number, public name:string){}
}

在guard目录下新建product-resolve.service.ts。ProductResolve类实现了Resolve接口。

Resolve也要声明一个范型,范型就是resolve要解析出来的数据的类型。

import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from "@angular/router";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs/Observable";
import { Product } from "../product/product.component";
@Injectable({providedIn: 'root'})
export class ProductResolve implements Resolve<Product>{
constructor(private router: Router,private service: GetDataService) {}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> | Promise<any> | any {
let productId: number = route.params["id"];
if (productId) { // 假设id存在去请求服务器
return new Product(1, "iPhone7");// 或者 return this.service.getProduct(参数);
} else { // id不在
this.router.navigate(["/home"]);
return undefined;
}
}
}

路由配置:Provider里声明,product路由里配置。

resolve是一个对象,对象里参数的名字就是想传入的参数的名字product,用ProductResolve来解析生成。

import { LoginGuard } from './guard/login-guard.service';
import { AuthGuard } from './guard/auth-guard.service';
import { UnsaveGuard } from './guard/unsave-guard.service';
import { ProductResolve } from './guard/product-resolve.service';
const routes: Routes = [
{ path: '', redirectTo : 'home',pathMatch:'full' },
{ path: 'home', component: HomeComponent },
{ path: 'product/:id', component: ProductComponent, children:[
{ path: '', component : ProductDescComponent },
{ path: 'seller/:id', component : SellerInfoComponent }],
resolve: { //resolve是一个对象
product : ProductResolve //想传入product,product由ProductResolve生成
}},
{ path: '**', component: Code404Component }
];
export class AppRoutingModule { }

修改一下product.component.ts 和模版,显示商品id和name。

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
@Component({
selector: 'app-product',
templateUrl: './product.component.html',
styleUrls: ['./product.component.css']
})
export class ProductComponent implements OnInit {
private productId: number;
private productName: string;
constructor(private routeInfo: ActivatedRoute) { }
ngOnInit() {
// this.routeInfo.params.subscribe((params: Params)=> this.productId=params["id"]);
this.routeInfo.data.subscribe(
(data:{product:Product})=>{
this.productId=data.product.id;
this.productName=data.product.name;
}
);
}
}
export class Product{
constructor(public id:number, public name:string){
}
}

<div class="product">
<p>
这里是商品信息组件
</p>
<p>
商品id是: {{productId}}
</p>
<p>
商品名称是: {{productName}}
</p>
<a [routerLink]="['./']">商品描述</a>
<a [routerLink]="['./seller',99]">销售员信息</a>
<router-outlet></router-outlet>
</div>

点商品详情链接,传入商品ID,在resolve守卫中是正确id,会返回一条商品数据。


点商品详情按钮,不传商品ID,是错误id,会直接跳转到主页。



注意点:



如果应用是小于angular 6 那么服务中



@Injectable({providedIn: 'root'})应该改为@Injectable()




在路由模块中就需要注入服务比如



@NgModule({

  providers: [LoginGuard,AuthGuard,UnsaveGuard,ProductResolve]

})

发表评论:
昵称

邮件地址 (选填)

个人主页 (选填)

内容