如何在Angular2中实现需要身份验证的组件

假設 / 條件 / 前提 / 假定

在Angular2中,通过@CanActivate注解来控制对组件的访问。

@Component({
  selector: 'some-component',
  template: '<div>...</div>',
})
@CanActivate((next, prev): Promise<boolean> | boolean => {
  return true;
})
export class SomeComponent {
}

可以通过一个返回布尔值或Promise布尔值的函数来访问@CanActivate。

一个问题

顺便说一下,认证状态(会话)通常是使用像SessionService这样的服务进行管理。

export class SessionService {

  isActive(): boolean {
    // 認証状況を判定して真偽値を返す何らかの処理
  }

}

嗯,当我们想要从@CanActivate中使用它时,实际上是无法使用的。

@Component({
  selector: 'some-component',
  template: '<div>...</div>',
})
@CanActivate(() => {
    // クラスの外側なので `this` は参照できないので下記はエラーとなる。
    return this._sessionService.isActive();
})
export class SomeComponent {

  constructor(private _sessionService: SessionService) {
  }

}

那么,麻烦了。

在解决这个问题之前

也许你会想要通过使用new来实例化SessionService来解决这个问题。但是,由于以下的原因,我们应该避免这样做。

    1. 在某些情况下,会期望SessionService具有状态并且是单例(不论是否对此有异议)。

有时SessionService可能会依赖于其他服务,如Http服务等。

因此,我们的目标是通过某种方式将SessionService的实例依赖注入到判断逻辑中。

Angular2中的依赖注入

我认为最好的方法是阅读@laco0416在去年年末发布的《了解Angular2 DI》博文,它对Angular2的DI进行了基本解释。

在Angular2中,依赖注入(DI)的实现是由一个名为Injector的类负责。在创建组件时,Injector会被必要地生成并与组件一起设置,并且它的层级结构与组件的父子关系保持一致。通过Injector的层级结构,可以在父组件中查找并注入所需的服务。

只要能够以某种方式从某处获取 Injector 的实例,目标就可以实现。

组件引用

顺便说一句,bootstrap(App, PROVIDERS) 的返回值是 Promise。实际上,这个 ComponentRef 包含了以下信息。

プロパティ内容location: ElementRef対象のHTML要素instance: anyコンポーネントのインスタンスcomponentType: Typeコンポーネントの型(コンストラクタ)injector: InjectorInjector のインスタンス(!)

实际上……

let app = bootstrap(App, PROVIDERS);
export const injector: Promise<Injector> = app.then(ref => ref.injector);

如果这样做,似乎可以获取到 Promise。我觉得已经离正确答案很近了,但是在上面的代码中,每个组件都依赖于应该是最顶层的 app.ts。

处理编码问题

到目前为止的总结。

@CanActivate からはコンポーネントに注入される各サービスにアクセスできない。
Angular2 の DI は Injector が行っている。

Injector のインスタンスは ComponentRef のインスタンスから取得できる。

ComponentRef のインスタンスは bootstrap 関数の戻り値から取得できる。

bootstrap の戻り値をそのまま公開(export)すると依存関係がおかしいことになる。

通过引入”延迟”,可以解决这个问题。

export class SessionService {
  isActive(): boolean {
    // 認証状況を判定して真偽値を返す何らかの処理
  }
}
import {Injector} from "angular2/core";
import {Deferred} from "ts-deferred";

export const deferredInjector = new Deferred<Injector>();
export const AppInjector: Promise<Injector> = deferredInjector.promise;
import {AppInjector} from "./app-injector";
import {SessionService} from "./session.service";

export function sessionIsActive(): Promise<boolean> {
  return AppInjector
    .then(injector => injector.get(SessionService))
    .then(sessionService => sessionService.isActive());
}
import {Component} from "angular2/core";
import {CanActivate} from "angular2/router";
import {sessionIsActive} from "./functions";
import {SessionService} from "./session.service";

@Component({
  selector: 'some-component',
  template: '<div>...</div>',
})
@CanActivate(sessionIsActive)
export class SomeComponent {
}
import {Component, ComponentRef} from "angular2/core";
import {bootstrap} from "angular2/platform/browser";
import {RouteConfig, ROUTER_DIRECTIVES} from "angular2/router";
import {deferredInjector} from "./app-injector";

@Component({
  template: '<router-outlet></router-outlet>',
  directives: [ROUTER_DIRECTIVES],
})
@RouteConfig({
  // ルート定義
})
class App {
}

let componentRef = bootstrap(App, [SessionService]);

componentRef.then(ref => deferredInjector.resolve(ref.injector));

在上述例子中,我使用了我自己编写的 ts-deferred 作为 Deferred 的实现,但是使用其他的实现也没有问题。

最后

如果您知道更好的解决方法,请务必在评论中告诉我们!

广告
将在 10 秒后关闭
bannerAds