はじめに
大規模な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の中身は以下のとおりです。
<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ファイルの中身は以下のようになっています。
import {Component} from 'angular2/core'
const template = `
<h2>page 1</h2>
`;
@Component({
template: template
})
export class Page1 {
}
import {Component} from 'angular2/core'
const template = `
<h2>page 2</h2>
`;
@Component({
template: template
})
export class Page2 {
}
Page1とPage2はtemplateから直接参照しないので、selector
は省略してもよさそうでした。
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 {
}
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の配列を与えます。
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へのリンクを作ってみます。
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内で処理を行ってからページ遷移を行いたい場合があると思います。
実装例は以下のとおりです。
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
に変更する例は以下のようになります。
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を使うサンプルを既に書いてくれていたのでとても助かりました。
Routerの詳細は公式のドキュメントも目を通してみてください。