この記事は、Angularをテーマとした日本初のカンファレンス 「ng-japan」のイベントレポート(第3回目)です。
はじめに
策定されているNew Routerは、Angular 1でもAngular 2でも利用可能で、大規模開発向けでもあるということが言われています。そのNew Routerに関する機能を見ていきたいと思います。このセッションではBrian Ford氏がNew Routerというタイトルで講演を行いました。
Angular 1 におけるNew Routerを使った大規模で維持しやすく、モジュラーなアプリケーションの作り方についてお話します。また、このRouterのAngular 2における挙動についても触れ、スムーズなアップグレードに活かす方法をお知らせします。
New Router – Brian Ford
多くの人がアプリケーションを作ったことがあり、Routingもよく知っていると思いますが、Routingのハイレベルな話をしていきたいと思います。Routingの基本的な考えは、URLとページをマッピングしていくことです。しかし、Angularの中でRoutingといった場合は、コンポーネントに対してマッピングをしていくという意味になります。つまり、URLの一部に対してUIの一部がマッピングされます。
もう一つRoutingの重要な機能として、Deep Linkingがあります。たとえば利用しているアプリケーションがURLを持っていて、それを友人にURLを送ることがあります。あるいはブックマーキングしページから離れ、戻ってきたときに同じ状態にするということがあります。
Angularの新しいルーティングは、こうした様々な過程をより簡単にするものです。
Routingの歴史
このプレゼンテーションの内容はNew Routerの話ではありますが、その前にRoutingの歴史を振り返ってみたいと思います。
- ngRoute – Very simple router with basic feature
- UI-Router – Mature, flexible router for Angular 1 that influenced the design of the New Router
- New Router – Designed with Angular 2 in mind, also works in Angular 1, and has features for migration
Angular 1がリリースされたときは、ngRouteがありました。このRoutingシステムはよかったのですが、非常にシンプルなユースケースのみサポートしていました。そうすると開発者のニーズがその範囲を越えていき、ニーズを満たすためにコミュニティの中でUI-Routeが生まれてきました。
UI-Routeはとてもよくできていて、Angular 1とよくあっていました。我々はAngular 2ともうまく機能しAngular 1ともうまくいくRoutingシステムを作りたかったのです。そしてUI-Routerを作ってきた開発者の知識を使って、Angular 2でもうまく機能するような新しいRouterを作ることになりました。結果として、Angular 1ともAngular 2ともうまく機能するRouterを作ることができました。
マイグレーションの話は後でお話しますが、Angular 1ともAngular 2ともうまく機能するRouterを作ることができましたので、マイグレーションも簡単にできるようになりました。そして、Angular1, 2とうまく連携するということと、Reusable, Composableにも優れているものになりました。
このNew Router開発者の生産性を上げていくことができます。多くのところで使えるものを作りました。その再利用性ですが、一つ簡単な例として、みなさんの会社の中で複数の場所で使えるロギングの仕組みを考えた場合がそうです。もう一つの例としては、リファクタリングしている間にこのコンポーネントを動かすことができるということです。
APIに関しても、他のRouteingを使っている開発者にとって、使い勝手のよいものにしました。あえて新しいものを発明するというよりも、いままで使っているRoutingの考えををAngularの中でも適用していく、ということにしました。Routingのプロジェクトには様々なものがありますが、新しいRoutingシステムは、特に目新しいものではありません。
以上のことを念頭に置き、新しいRouterについてどういうところで使えるかお話していきたいと思います。
Basic Use
$routeConfig
次の例は、あまりおもしろいWebアプリケーションではありません。しかし、アプリケーションのメニューの部分をクリックするとURLが変化します。
URLとしてハッシュバンを使っていますが、HTML5を使うこともできます。このアプリケーションのコードが下記になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
angular.model('example', [ 'example.goobye', 'example.welcame', 'ngAnimate', 'ngNewRouter' ]) .controller('AppController', ['$router', AppController]); AppController.$routeConfig = [ { path: '/', redirectTo: '/welcome' }, { path: '/welcame', component: 'welcome' }, { path: '/goodbye', component: 'goodbye' } ]; function AppController($router) { this.greeting = 'Hello'; } |
このコードの中で興味深いところだけをピックアップしご紹介します。
1 2 3 4 5 |
AppController.$routeConfig = [ { path: '/', redirectTo: '/welcome' }, { path: '/welcame', component: 'welcome' }, { path: '/goodbye', component: 'goodbye' } ]; |
このコードが、ルーティングをどう設定するかのコンフィグレーションになります。これは単純なJavaScriptオブジェクトですが、$routeConfigのプロパティをコントローラーに対してAttributeをつけていきます。これにより、コンポーネントに対してどうURLにマッピングするかをAngularに伝えていきます。
たとえば「/」が「/welcame」にリダイレクトするように設定しています。そして「/welcame」はコンポーネントの「/welcame」に「/goodbay」はコンポーネントの「/goodbay」にいくということになります。
ng-link
このアプリケーションに対応しているHTMLがこちらです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<!doctype html> <html lang="en"> <head> <base href="/examples/angular-1/animation/"> </head> <body ng-app="example" ng-strict-di ng-controller="AppController as app"> <nav> <ul> <li><a ng-link="welcame">welcame</a></li> <li><a ng-link="goodbye">goodbye</a></li> </ul> </nav> <ng-viewport></ng-viewport> <script src="/node_modules/angular/angular.js"></script> ・・・ </body> </html> |
ここでも興味深い機能をご紹介します。ng-linkのAttributeですが、値がwelcameになっていてWelcameのコンポーネントに結びついています。そして、このディレクティブは自動的にhrefタグを生成していきます。
ngViewport
もう一つ面白いのがngViewportディレクティブです。ngViewportはプレスフォルダに対しアクセルします。そしてプレスフォルダの中はコンテンツに基づいて管理されています。
Sibling Routes
今度はもう少し複雑な例を見てみます。アイデアを表した概念図はこちらになります。
このアプリケーションでは、URLに基いてパネルをリアレンジすることができます。もしURLが/post/usersになっていたらpostは左、usersは右に見せることができます。URLが逆になっていたら、パネルも逆に表示されます。この例のコードの例を見ていきます。
1 2 3 4 5 |
AppController.$routeConfig = [ { path: '/', redirectTo: '/users/posts' }, { path: '/users/posts', components: { left: 'users', right: 'posts' } }, { path: '/posts/users', components: { left: 'posts', right: 'users' } } ]; |
1 2 3 4 5 6 7 8 |
<nav> <a href="./users/posts">users then posts</a> <a href="./posts/users">posts then users</a> </nav> <div class="container"> <div ng-viewport="left"></div> <div ng-viewport="right"></div> </div> |
ここで重要なのは、複数のコンポーネントに対して、名前で指定することができるというところです。ngViewportは、アトリビュートとしても利用することができます。このアトリビュートの値ですが、コンフィグレーションの中で使われているものと対応することができます。
Lifecycle Hooks
この機能を使うために、例を紹介します。この例ではユーザが保存しない場合、「saveしますか?」と問い合わせを出します。もしユーザが保存するという場合は、ナビゲータが出てきて指示どおりに動かくことができます。「saveしない」ということになれば、そのまま処理を行いデータを保存まま進めることもできます。ここでも重要なところを見ていきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
angular.module('myApp', [ 'ngNewRouter', 'myApp.index', 'myApp.editPost', 'myApp.saveModel' ]) .controller('AppController', ['$router', AppController]) .factory('post', postsFactory); AppController.$routeConfig = [ { path: '/', component: 'index' }, { path: '/post/:id', component: 'editPost' } ]; function AppController($router) { ... } function postsFactory() { return { '1': { title: 'First Post', content: 'I wrote this first' }, '2': { title: 'Second Post', content: 'I wrote this second' } }; } |
Configuration DSLですが、ngRouteと似たものになります。
1 |
{ path: '/post/:id', component: 'editPost' } |
この例ではIDを持っていて、コンポーネントに対してシリアライズされていきます。そして、「editPost」というコンポーネントにナビゲーションされます。対応するコンポーネントがこちらです。
1 2 3 4 5 6 7 8 9 |
<h1>Post Index</h1> <ul> <li ng-repeat="(id, item) in index.posts"> <a ng-link="editPost({ id: id })"> Edit {{item.title}} </a> </li> </ul> |
Deep Linking DSLは、パラメータを使っています。
1 |
<a ng-link="editPost({ id: id })"> |
この場合は、特定のリンクに対してidを付与しています。今度はeditPostコンポーネントのコードを見ていきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
angular.module('myApp.editPost', []) .controller('EditPostController', ['$routeParams', 'posts', ... function EditPostController($routeParams, posts, saveModal) { this.saveModal = saveModal; this.post = posts[$routeParams.id]; this.newContent = this.post.content; } EditPostController.prototype.canDeactivate = function () { if (this.newContent === this.post.content) { return true; } return this.saveModel.getResponse(); } |
$routeparameterがコントローラーにどう渡されていくかを見ていきます。
canDeactivateメソッドを使っていまが、これはAngularのルーティングシステムが理解できる特定のメソッドになります。URL変更やナビゲータのフックの一部として、このフックを実行してきます。ここには現れていませんが、getResponse()でポップアップを見せていくこともできます。この関数はプロミスを返します。このプロミスですがユーザがどのボタンを押したかによって、リゾルブあるいはリジェクトになっていきます。
Angularのシステムは、このプロミスの値が何になるかわかるようになります。このシステムにより、より大きなアプリケーションの中で複雑なものをコンポーズすることができます。そして、今述べたように、Routerは異なるものを組み合わせていくことができます。
ナビゲーションがスタートしたときに、既存のコンポーネントがリアクタベートされることができるか、リユースすることができるのかを決めていきます。答えが「Yes」であれば、このナビゲーションは完了していきます。
もしリアクティベートできないのであれば、アクティベートしていきます。そしてリアクティベート可能なら新しいコンポーネントのインスタンスを生成していきます。次のコンポーネントに対して、アクティベート可能か聞いていきます。新しいコンポーネントがアクティベート可能なら、古いコンポーネントに対しリアクティベートしていきます。
最後に、新しいコンポーネントにフックしてviewportのプレスフォルダーの中に入っていきます。
マイグレーション
新しいRouterは、Angular 1からAngular 2へのマイグレーションを簡単にするという話をしました。Routerというのはアプリケーションにとって最もトップクラスにあたります。マイグレーションする上でRouterの2つの重要な機能があります。
- Angular 1とAngular 2とで同じコンフィグレーションを使っていく
- 新しいRouterを使っていくことで一部はAngular 1で他の一部Angular 2で動く
上がAngular 2、下はAngular 1です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@component({ selector: 'hello-app', ... }) @Template({ ... }) @RouteConfig([ { path: '/' , redirectTo: '/home' }, { path: '/home' , component: 'index' } ]) class HelloComponent { greeting: string; constructor(service: GreetingService) { this.greeting = service.greeting; } changeGreeting() { this.greeting = 'howdy'; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
angular.module('hello-app', [].controller( ... ); HelloController.$routeConfig = [ { path: '/' , redirectTo: '/home' }, { path: '/home' , component: 'index' } ]; function HelloController(greetingService) { this.greeting = greetingService.greeting; } HelloController.prototype.changeGreeting = function() { this.greeting = 'howdy'; } } |
見た目が違うように見えますが、ルーティングのコンフィグレーションという観点では変わりありません。
2つの目重要なこととしてAngular 1とAngular 2とで平行して動かすことができます。Angular 2が良い場合はAngular 2で、Angular 1がよければAngular 1で動かすことができます。
大きなアプリケーションを作っている場合、一度にAngular 2に移行することは難しいので使いたい部分だけ使っていけばいいです。つまり部分的にAngular 2にしていくことが可能になります。
これは、大きなアプリケーションの場合、徐々にAngular 2にしていくことが可能なことを示しています。最後にAngular 1として残った複雑なものは、すべてが終わった後にゆっくり変えていくなど、いろいろなやり方でマイグレーションしていくことができます。
Future
New Routerですが、すべてがまだ完全に終わったわけではありません。現在のカレントバージョンは0.5になっています。新しいRouterが出せるまでにもう少し作業しなければならないのですが、New Router 1.0はAngular 1.4と同時に出そうと思っています。なのでタイミングとしては来週にリリースする予定になっています。
その後New Routerのリリースに関しては、Angular 1、Angular 2とも独立したかたちでリリースしていくことになります。
New Routerに関して情報がほしい人はこちらを見てください:
- http://goo.gl/rC4xcZ
- https://github.com/angular/angular.js/issues/11015
- http://angular.github.io/router/
CodeIQとの連動企画!
AngularJS雑学、豆知識を問う問題です。腕試しに、もしくは理解度チェックに是非ご活用ください!こちらから問題にチャレンジ!
問題:知ってる?AngularJS雑学
プレゼンテーション資料
今回取り上げたプレゼンテーションの資料は、以下で公開されています。合わせてご覧ください。
セッション動画
当日のセッションはYouTubeで公開されています。逐次通訳付きなので、ぜひご覧ください!