如何在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来解决这个问题。但是,由于以下的原因,我们应该避免这样做。
-
- 在某些情况下,会期望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: Injector
Injector
のインスタンス(!)实际上……
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 的实现,但是使用其他的实现也没有问题。
最后
如果您知道更好的解决方法,请务必在评论中告诉我们!