8

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

路线示例:

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

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

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

4

3 回答 3

11

问题

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

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


解决方案

这个想法是创建一个主守卫并让主守卫处理其他守卫的执行。

在这种情况下,路由配置将包含master guard 作为唯一的 guard

要让主守卫知道要为特定路线触发的守卫,请dataRoute.

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

然后可以使用防护中ActivatedRouteSnapshot的方法参数在防护中访问数据canActivate

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

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


例子

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);
    }
}

挑战

这种方法的挑战之一是重构现有的路由模型。但是,它可以部分完成,因为更改是非破坏性的。

我希望这有帮助。

于 2017-12-07T14:48:44.607 回答
1

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

于 2019-02-28T16:59:14.763 回答
0

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

多个 canActivate 守卫在第一次失败时全部运行

于 2019-11-27T20:52:06.933 回答