Angular2のRouterを触ってみる


# はじめに

大規模なSPAでは必要不可欠なルーティングの機能をAngular2で試してみます。
Angular2の2.0.0-beta.0を使います。

# Router

まずは最小限の例を示しましょう。
beta.0で更新された5 MIN QUICKSTARTを参考にディレクトリ構成は以下のようにしています。

```
my-app
├ node_modules
├ app
│ ├ app.component.ts
│ ├ page1.component.ts
│ ├ page2.component.ts
│ └ boot.ts
├ index.html
├ package.json
└ tsconfig.json
```

index.htmlの中身は以下のとおりです。

```index.html
<html>
  <head>
    <title>Angular 2 QuickStart</title>
    <script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
    <script src="node_modules/systemjs/dist/system.src.js"></script>
    <script src="node_modules/rxjs/bundles/Rx.js"></script>
    <script src="node_modules/angular2/bundles/angular2.dev.js"></script>
    <script src="node_modules/angular2/bundles/router.dev.js"></script>
    <script>
      System.config({
        packages: {
          app: {
            format: 'register',
            defaultExtension: 'js'
          }
        }
      });
      System.import('app/boot')
        .then(null, console.error.bind(console));
    </script>
    <base href="/">
  </head>
  <body>
    <my-app>Loading...</my-app>
  </body>
</html>
```

5 MIN QUICKSTARTのindex.htmlに、`router.dev.js`を読み込む`script`要素と`base`要素を追加しています。

4つのTypeScriptファイルの中身は以下のようになっています。

```app/page1.component.ts
import {Component} from 'angular2/core'

const template = `
<h2>page 1</h2>
`;

@Component({
  template: template
})
export class Page1 {
}
```

```app/page2.component.ts
import {Component} from 'angular2/core'

const template = `
<h2>page 2</h2>
`;

@Component({
  template: template
})
export class Page2 {
}
```

Page1とPage2はtemplateから直接参照しないので、`selector`は省略してもよさそうでした。

```app/app.component.ts
import {Component} from 'angular2/core'
import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router'
import {Page1} from './page1.component'
import {Page2} from './page2.component'

const template = `
<router-outlet></router-outlet>
`;

@Component({
  selector: 'my-app',
  template: template,
  directives: [ROUTER_DIRECTIVES]
})
@RouteConfig([
  {
    useAsDefault: true,
    path: '/page1',
    component: Page1
  },
  {
    path: '/page2',
    component: Page2
  }
])
export class AppComponent {
}
```

```app/boot.ts
import {bootstrap} from 'angular2/platform/browser'
import {ROUTER_PROVIDERS} from 'angular2/router'
import {AppComponent} from './app.component'

bootstrap(AppComponent, [
  ROUTER_PROVIDERS
]);
```

ブラウザで開くと`http://localhost:3000/page1`に遷移し「page 1」と表示されているはずです。
URLを入力し`http://localhost:3000/page2`に遷移すると表示内容が「page 2」に変わるかと思います。

とりあえず大事なのは、app.component.tsの`@RouteConfig`です。
引数には[Route](https://angular.io/docs/ts/latest/api/router/Route-class.html)の配列を与えます。

Routeの引数に必須なのは`path`と`component`で、`path`はURLに反映されるpathの名前、`component`はこのRouteに対応するComponentです。
Page1へのRouteに`useAsDefault: true`を与えることで、`http://localhost:3000/`にアクセスしたときに`http://localhost:3000/page1`に遷移するようになっています。

Page1、Page2の内容は、templateの`<router-outlet></router-outlet>`の箇所に表示されます。
`<router-outlet></router-outlet>`を使うには、`angular/router`からimportする`ROUTER_DIRECTIVES`が必要です。

# routerLink

さて、上のままではURLを直接打ち込まないとページ遷移ができないので不便です。
routerLinkを使ってRouteへのリンクを作ってみます。

```app/app.component.ts
import {Component} from 'angular2/core'
import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router'
import {Page1} from './page1.component'
import {Page2} from './page2.component'

const template = `
<a [routerLink]="['Spam']">page1</a>
<a [routerLink]="['Ham']">page2</a>
<router-outlet></router-outlet>
`;

@Component({
  selector: 'my-app',
  template: template,
  directives: [ROUTER_DIRECTIVES]
})
@RouteConfig([
  {
    useAsDefault: true,
    path: '/page1',
    component: Page1,
    name: 'Spam'
  },
  {
    path: '/page2',
    component: Page2,
    name: 'Ham'
  }
])
export class AppComponent {
}
```

変更点は2箇所です。
まずRouteのパラメータに`name`を与えます。
次にtemplateに`a`要素を追加し、属性に`[routerLink]="['Spam']"`のように書きます。
ここでの「Spam」が、Routeの`name`の「Spam」に対応します。

また、alpha版の資料だと`router-link`と書かれていたりしますが、`routerLink`に変更になっているので気をつけてください。

# router.navigate

formをsubmitされたときなど、Component内で処理を行ってからページ遷移を行いたい場合があると思います。
実装例は以下のとおりです。

```app/page2.component.ts
import {Component} from 'angular2/core'
import {Router} from 'angular2/router'

const template = `
<h2>page 2</h2>
<button (click)="goPage1()">go to page1</button>
`;

@Component({
  template: template
})
export class Page2 {
  constructor(public router: Router) {
  }

  goPage1() {
    console.log('go to page1');
    this.router.navigate(['Spam']);
  }
}
```

router.navigateの引数はtemplateでの`routerLink`と同じなようです。

# LocationStrategy

Angular2ではPathをURLにするときに、実際に`https://example.com/page1`と`https://example.com/#/page1`のどちらにするかを`LocationStrategy`を使って設定できます。
Angular1の`$locationProvider.html5Mode`と同じような感じです。
前者は`PathLocationStrategy`、後者は`HashLocationStrategy`と呼びます。

デフォルトでは`PathLocationStrategy`なので、`HashLocationStrategy`に変更する例は以下のようになります。

```app/app.component.ts
import {provide} from 'angular2/core'
import {bootstrap} from 'angular2/platform/browser'
import {ROUTER_PROVIDERS, LocationStrategy, HashLocationStrategy} from 'angular2/router'
import {AppComponent} from './app.component'

bootstrap(AppComponent, [
  ROUTER_PROVIDERS,
  provide(LocationStrategy, {useClass: HashLocationStrategy})
]);
```

# Lifecycle Hooks

Angular2ではLifecycle HooksによってComponentの状態変更のタイミングで処理をはさむことができます。
RouterのLifecycle Hookは`OnActivate`、`OnReuse`、`OnDeactivate`、`CanReuse`、`CanDeactivate`の5種類があります。

各HookはInterfaceとして提供されており、ComponentでInterfaceのメソッドを実装することになります。
以下は簡単な例です。

```
import {Component} from 'angular2/core'
import {OnActivate, OnReuse, OnDeactivate, CanReuse, CanDeactivate} from 'angular2/router'

const template = `
<h2>page 1</h2>
`;

@Component({
  template: template
})
export class Page1 implements OnActivate, OnReuse, OnDeactivate, CanReuse, CanDeactivate {
  routerOnActivate() {
    console.log('onActivate');
  }

  routerOnReuse() {
    console.log('onReuse');
  }

  routerOnDeactivate() {
    console.log('onDeactivate');
  }

  routerCanReuse() {
    return confirm('can reuse?');
  }

  routerCanDeactivate() {
    return confirm('can deactivate?');
  }
}
```

`OnActivate`のInterfaceに`routerOnActivate`メソッドが対応するという感じになっています。
`routerCanReuse`と`routerCanDeactivate`は真偽値またはPromiseを返すようになっています。
今回は試していませんが、Promiseを返すことで非同期的な処理も行うことができます。

# おわりに

本稿では、Angular2のRouterでよく使いそうな機能などを試してみました。
今年の春頃にAngular1向けのComponent Routerを触ってみましたが、そこから更に洗練された印象を受けます。

本稿を書くにあたって、@ovrmrw さんがRouterを使うサンプルを既に書いてくれていたのでとても助かりました。

* [初心者がAngular2で嵌まったり解決したりサンプルコード書いたりしてみた。](http://overmorrow.hatenablog.com/entry/2015/12/09/000000)

Routerの詳細は公式のドキュメントも目を通してみてください。

* [Routing & Navigation](https://angular.io/docs/ts/latest/guide/router.html)