angular 中的路由守卫CanActivate、CanDeactivate、Resolve

2019-2-26 Jon js库

一、路由守卫
当用户满足一定条件才被允许进入或者离开一个路由。
路由守卫场景:
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]
})

标签: 路由

分享这篇文章
赞助鼓励:如果觉得内容对您有所帮助,您可以支付宝(左)或微信(右):

声明:如无特殊注明,所有博客文章版权皆属于作者,转载使用时请注明出处。谢谢!

发表评论:

皖ICP备15010162号-1 ©2015-2022 知向前端
qq:1614245331 邮箱:13515678147@163.com Powered by emlog sitemap