Angular2 で認証が必要なコンポーネントを実装する方法

## 前提
Angular2 ではコンポーネントへのアクセス制御を `@CanActivate` アノテーションで行います。

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

`@CanActivate` へはアクセス可否を `boolean` または `Promise<boolean>` で返す関数を渡します。


## 課題
ところで、認証状況（セッション）は `SessionService` のようなサービスを使って管理することが多いかと思います。

```ts:session.service.ts
export class SessionService {

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

}
```

さて、これを `@CanActivate` から使おうとすると、まぁ、使えないわけです。

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

  constructor(private _sessionService: SessionService) {
  }

}
```

さて、困った。

## 解決編、その前に
例えば `SessionService` を `new` でインスタンス化して使えばいいんじゃないか、と思うかもしれません。しかし、それは次のような理由で避けるべきです。

1. （その是非は別として）`SessionService` が状態を持っており、シングルトンであることを期待している場合がある。
2. `SessionService` が別のサービス（例えば `Http`）などに依存している場合がある。

そんなわけで、なんとかして判定ロジックに `SessionService` のインスタンスを DI してやる、というのが目指すべき方向性です。

### Angular2 の DI
Angular2 の DI についての基本的な説明は @laco0416 さんが昨年末のアドベントカレンダーに投稿した [Angular2のDIを知る](http://qiita.com/laco0416/items/61eed550d1f6070b36ab) を読んでいただくのが一番かと思います。

実際に Angular2 における DI を担っているのは `Injector` というクラスです。コンポーネントを生成する場合、必ずこの `Injector` もセットで生成され、コンポーネントの親子構造と同じ `Injector` の親子構造を持ちます。注入するサービスを親コンポーネントを辿って見つける仕組みは、この `Injector` の親子構造によって実現されています。

ということはなんとかして `Injector` のインスタンスをどこかから持ってくることができれば、目的が達成できそうです。

### `ComponentRef`
ところで `bootstrap(App, PROVIDERS)` の戻り値は `Promise<ComponentRef>` です。実はこの `ComponentRef` は次のような情報を持っています。

| プロパティ             | 内容                                 |
| ---------------------- | ------------------------------------ |
| `location: ElementRef` | 対象のHTML要素                       |
| `instance: any`        | コンポーネントのインスタンス         |
| `componentType: Type`  | コンポーネントの型（コンストラクタ） |
| `injector: Injector`   | `Injector` のインスタンス（！）      |

つまり……

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

とすれば `Promise<Injector>` が取得できそうです。正解にかなり近づきました気がしますが、上記のコードのままでは最上位層であるはずの `app.ts` に各コンポーネントが依存してしまいます。


## 解決編
ここまでのまとめ。

- `@CanActivate` からはコンポーネントに注入される各サービスにアクセスできない。
- Angular2 の DI は `Injector` が行っている。
- `Injector` のインスタンスは `ComponentRef` のインスタンスから取得できる。
- `ComponentRef` のインスタンスは `bootstrap` 関数の戻り値から取得できる。
- `bootstrap` の戻り値をそのまま公開（`export`）すると依存関係がおかしいことになる。

そこで次のように `Deferred` を導入することで解決します。

```ts:session.service.ts
export class SessionService {
  isActive(): boolean {
    // 認証状況を判定して真偽値を返す何らかの処理
  }
}
```

```ts:app-injector.ts
import {Injector} from "angular2/core";
import {Deferred} from "ts-deferred";

export const deferredInjector = new Deferred<Injector>();
export const AppInjector: Promise<Injector> = deferredInjector.promise;
```

```ts:functions.ts
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());
}
```

```ts:some.component.ts
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 {
}
```

```ts:app.ts
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));
```

上記例では `Deferred` の実装として拙作の [ts-deferred](https://github.com/shogogg/ts-deferred) を利用していますが、別の実装を利用しても問題ありません。

## 最後に
もっといい解決方法をご存じの方は、ぜひコメントで教えてください！