按顺序执行多个异步路由守卫

Posted

技术标签:

【中文标题】按顺序执行多个异步路由守卫【英文标题】:Execute Multiple Asynchronous Route Guards in Order 【发布时间】:2017-11-22 06:59:37 【问题描述】:

我知道当 canActivate 函数返回一个简单的boolean 时,角度路由守卫会按指定的顺序执行,但是,如果守卫返回类型为Observable<boolean>Promise<boolean> 怎么办?

路线示例:


    path: 'confirm',
    canActivate: [AuthGuard, SessionExpiredAuthGuard, CheckoutAuthGuard],
    component: CheckoutReviewOrderComponent
,

SessionExpiredAuthGuard 和 CheckoutAuthGuard 都返回类型 Observable<boolean>。我不希望在 SessionExpiredAuthGuard 完成从异步 http 请求中检索其数据之前执行 CheckoutAuthGuard。

有没有办法强制这些异步守卫按顺序执行?

【问题讨论】:

你真的见过他们的行为不正常吗?我认为它们已通过concatAll 解决,这会将它们称为串联而不是并联。 是的。有时 CheckoutAuthGuard 在前一个之前完成。 @jonrsharpe,但是我在我的 SessionExpiredAuthGuard 中使用finally 方法...这会在整个 observables 堆栈上最后执行吗? 【参考方案1】:

问题

首先,Angular 不支持串联调用守卫的功能。因此,如果第一个守卫是异步的并且正在尝试进行 ajax 调用,那么所有剩余的守卫甚至在守卫 1 中的 ajax 请求完成之前都会被触发。

我遇到了类似的问题,这就是我解决它的方法 -


解决方案

想法是创建一个ma​​ster guard,让master guard来处理其他guard的执行。

在这种情况下,路由配置将包含主守卫作为唯一守卫

要让主守卫知道特定路由要触发的守卫,请在Route 中添加data 属性。

data 属性是一个键值对,允许我们在路由中附加数据。

然后可以使用警卫中canActivate 方法的ActivatedRouteSnapshot 参数在警卫中访问数据。

该解决方案看起来很复杂,但一旦将其集成到应用程序中,它将确保警卫正常工作。

以下示例解释了这种方法 -


示例

1.用于映射所有应用程序守卫的常量对象 -

export const GUARDS = 
    GUARD1: "GUARD1",
    GUARD2: "GUARD2",
    GUARD3: "GUARD3",
    GUARD4: "GUARD4",

2。应用程序防护 -

import  Injectable  from "@angular/core";
import  Guard4DependencyService  from "./guard4dependency";

@Injectable()
export class Guard4 implements CanActivate 
    //A  guard with dependency
    constructor(private _Guard4DependencyService:  Guard4DependencyService) 

    canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> 
        return new Promise((resolve: Function, reject: Function) => 
            //logic of guard 4 here
            if (this._Guard4DependencyService.valid()) 
                resolve(true);
             else 
                reject(false);
            
        );
    

3。路由配置 -

import  Route  from "@angular/router";
import  View1Component  from "./view1";
import  View2Component  from "./view2";
import  MasterGuard, GUARDS  from "./master-guard";
export const routes: Route[] = [
    
        path: "view1",
        component: View1Component,
        //attach master guard here
        canActivate: [MasterGuard],
        //this is the data object which will be used by 
        //masteer guard to execute guard1 and guard 2
        data: 
            guards: [
                GUARDS.GUARD1,
                GUARDS.GUARD2
            ]
        
    ,
    
        path: "view2",
        component: View2Component,
        //attach master guard here
        canActivate: [MasterGuard],
        //this is the data object which will be used by 
        //masteer guard to execute guard1, guard 2, guard 3 & guard 4
        data: 
            guards: [
                GUARDS.GUARD1,
                GUARDS.GUARD2,
                GUARDS.GUARD3,
                GUARDS.GUARD4
            ]
        
    
];

4.守护大师 -

import  Injectable  from "@angular/core";
import  CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router  from "@angular/router";

//import all the guards in the application
import  Guard1  from "./guard1";
import  Guard2  from "./guard2";
import  Guard3  from "./guard3";
import  Guard4  from "./guard4";

import  Guard4DependencyService  from "./guard4dependency";

@Injectable()
export class MasterGuard implements CanActivate 

    //you may need to include dependencies of individual guards if specified in guard constructor
    constructor(private _Guard4DependencyService:  Guard4DependencyService) 

    private route: ActivatedRouteSnapshot;
    private state: RouterStateSnapshot;

    //This method gets triggered when the route is hit
    public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> 

        this.route = route;
        this.state = state;

        if (!route.data) 
            Promise.resolve(true);
            return;
        

        //this.route.data.guards is an array of strings set in routing configuration

        if (!this.route.data.guards || !this.route.data.guards.length) 
            Promise.resolve(true);
            return;
        
        return this.executeGuards();
    

    //Execute the guards sent in the route data 
    private executeGuards(guardIndex: number = 0): Promise<boolean> 
        return this.activateGuard(this.route.data.guards[guardIndex])
            .then(() => 
                if (guardIndex < this.route.data.guards.length - 1) 
                    return this.executeGuards(guardIndex + 1);
                 else 
                    return Promise.resolve(true);
                
            )
            .catch(() => 
                return Promise.reject(false);
            );
    

    //Create an instance of the guard and fire canActivate method returning a promise
    private activateGuard(guardKey: string): Promise<boolean> 

        let guard: Guard1 | Guard2 | Guard3 | Guard4;

        switch (guardKey) 
            case GUARDS.GUARD1:
                guard = new Guard1();
                break;
            case GUARDS.GUARD2:
                guard = new Guard2();
                break;
            case GUARDS.GUARD3:
                guard = new Guard3();
                break;
            case GUARDS.GUARD4:
                guard = new Guard4(this._Guard4DependencyService);
                break;
            default:
                break;
        
        return guard.canActivate(this.route, this.state);
    

挑战

这种方法的挑战之一是重构现有的路由模型。但是,由于更改不会中断,因此可以部分完成。

我希望这会有所帮助。

【讨论】:

我在“return guard.canActivate(this.route, this.state);”上遇到错误错误是:键入'布尔值| Observable' 不可分配给类型 'boolean'。类型 'Observable' 不能分配给类型 'boolean'。 您可以通过 - stackblitz.com 发布代码吗? 解决了!只是将 activateGuard 更改为 async 并将这一行 return guard.canActivate(this.route) 修改为 Promise【参考方案2】:

除了回答planet_hunter,我也敢分享一点改进master-guard

【讨论】:

【参考方案3】:

这是我受@planet_hunter 启发的解决方案,它与 Angular 8 的 CanActivate 签名完全兼容:

Multiple canActivate guards all run when first fails

【讨论】:

以上是关于按顺序执行多个异步路由守卫的主要内容,如果未能解决你的问题,请参考以下文章

路由守卫钩子函数和执行顺序

路由守卫钩子函数和执行顺序

路由守卫钩子函数和执行顺序

路由守卫和拦截器

Vue路由守卫详解

路由导航守卫(导航钩子)