当用户满足一定条件才被允许进入或者离开一个路由。
路由守卫场景:
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方法第一个参数就是接口指定的范型类型的组件,根据这个要保护的组件的状态,或者调用方法来决定用户是否能够离开。
配置路由,同样先加到provider,再配置路由。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('你还没有保存,确定要离开吗?'); } }
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]
})
发表评论: