Angular RouteGuard/动态导航

Angular RouteGuard / Dynamic Navigation

我有一个应用程序,我的导航栏根据它们所在的产品 'area' 发生变化。我正在使用 Angulars Route Guards 来确保检查它们的访问权限,以便它们只能访问路由他们可以访问。这很好用!

在我的 app-routing-module.ts 中,我(尝试)变得聪明并利用 ActivatedRouteSnapshot 获取所有 child link,然后为其构建导航。我想做的,也是使用 Route Guard 来决定 child link 是否应该显示。

// 守卫

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { environment } from '../../environments/environment';
import { MeService } from '../shared/services/me.service';

@Injectable()
export class AdminGuard implements CanActivate {
  constructor(private _meService: MeService) {}
  async canActivate(
    next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
    const user = await this._meService.getCurrentUser();
    if (user && user.isUserAdminForCompany) {
      return true;
    } else {
      return false;
    }
  }
}

// 路线

export const routes: Routes = [
  { path: '', redirectTo: 'route1', pathMatch: 'full' },
  { path: 'route1', component: MyComponent,
    children: [
      { path: '', redirectTo: 'overview', pathMatch: 'full' },
      { path: 'overview', component: Overview },
      { path: 'specs', component: Specs, canActivate: [ AdminGuard ] }
    ]
  }
];

所以,一旦有人点击 MyComponent,我就会获取 child 路线并从中制作导航栏。如果 AdminGuard returns 为假,是否可以使用某种指令或某种方式利用 /spec 路径上的 AdminGuard 来隐藏 URL?由于我的 some/more 守卫需要对服务器进行某种异步调用或某些其他服务依赖项,因此我不能简单地在 *ngIf 或其他内容中调用 guard.canActivate

我很确定它不存在,但似乎需要这样的设置:

<a [routerLink]="child.path" [canActivate]="child.guards">{{child.name}}</a>

更新 我最终只是在 angular 存储库上打开了一个 GitHub 功能请求。此功能似乎不存在(以开箱即用的方式)。在找到更好的解决方案之前,我将制作一个自定义指令,该指令将 运行 Guards 中的逻辑来评估是否应该公开某些内容。

https://github.com/angular/angular/issues/25342

为什么不在一个函数中添加以下代码,然后在具有 routerLinks 的同一组件中的 *ngIf 中传递该函数。

const user = await this._meService.getCurrentUser();
if (user && user.isUserAdminForCompany) {
  return true;
} else {
  return false;
}

这将解决您的问题。方法略有改变,这将隐藏路由器链接本身

这是我最终选择的。由于没有任何 'out of the box' 方法来利用守卫来完成我想做的事情,我只是制作了一个自定义指令。

关于此解决方案,我要注意的一件事是我讨厌以下两件事(我最终会改变)。

  1. 如果您的 Guards 有任何重定向功能,您必须更改它,以便 Guard 仅 returns true/false。如果它在 Guard 失败时重定向页面,那么该指令将最终重定向你而不是仅仅隐藏元素

  2. this._elementRef.nativeElement.style.display = hasAccess ? 'block' : 'none'; 有比简单隐藏更好的解决方案。它应该表现得像 *ngIf,它甚至根本不渲染元素,除非它的计算结果为真。

实施:

<div appGuard [guards]="myGuardsArray">Something you want to hide .... </div>

指令:

import { Directive, ElementRef, Injector, Input, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';

@Directive({
  selector: '[appGuard]'
})
export class GuardDirective implements OnInit {
  @Input() guards: any[];
  private readonly _elementRef: ElementRef;
  private readonly _activatedRoute: ActivatedRoute;
  private readonly _router: Router;
  private readonly _injector: Injector;
  constructor(_elementRef: ElementRef, _activatedRoute: ActivatedRoute, _router: Router,
              _injector: Injector) {
    this._elementRef = _elementRef;
    this._activatedRoute = _activatedRoute;
    this._router = _router;
    this._injector = _injector;
  }
  async ngOnInit(): Promise<void> {
    const canActivateInstances = this.guards.map( g => this._injector.get(g));
    const results = await Promise.all(canActivateInstances.map( ca => ca.canActivate(this._activatedRoute.snapshot, this._router.routerState.snapshot)));
    const hasAccess = results.find( r => !r) === false ? false : true;
    this._elementRef.nativeElement.style.display = hasAccess ? 'block' : 'none';
  }
}

更新

确定如何处理重定向的简单解决方案:

async canActivate(
    next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
    const user = await this._meService.getCurrentUser();
    const result = user && user.isUserAdminForCompany;
    if (next.routeConfig && next.routeConfig.canActivate.find( r => r.name === 'NameOfGuard') && !result) {
  window.location.href = `${environment.webRoot}/sign-in`;
}
    return result;
  }