初投稿です。KENです。最近AngularJS
でハイブリッドアプリを作るionic
をがっつり触っています。Angular2
もionic2
も楽しみだけど、productionにはまだ早いので当面は1.x系になりそうですね。今回はionic
で割と表舞台に登場していない地雷を踏んでしまったので対処法を紹介します。
はじめに
最近の(といっても考え自体は昔からありますが)SPAは画面のそれぞれの部品をComponent
として考えて、一つ一つの部品を独立したものとして扱い、再利用性も高めようっていう動きが活発です。Angular2
はがっつりComponent
指向で書きやすく作られている印象ですけど、AngularJS
はComponent
指向で作らなくてもService
とController
を使ってそれとなく書けちゃうんですよね。しかしながらAngular2
に移行しやすくしたいならComponent
指向で作るのが良いかと思います(実際の1.x系から2への移行メソッドは執筆時点で公開されていない為、これはあくまで個人的な意見です)。
結局何が言いたいかというと、今ionic
で作っているプロジェクトもこの思想のもと、Directive
にできるものはDirective
にしながら開発しています。
発生した問題
なんでもかんでもDirective
にして、テストのしやすさ、プロジェクト管理のしやすさはやっぱり格段に違います。が、 副作用 がありました。ionic
でアプリを作ったことがある人は知っているかと思いますが、ionic
は独自でブラウザのHistory
を管理して、割といい感じに、画面遷移した時にヘッダ部分に「戻る」ボタンを自動で表示してくれます。
なぜかこれが自動で表示されていないことに気づきました。ただ単に表示されていないだけじゃなくて、2回目以降にその画面を表示すると出現しているんです。(↓参照)
Back button not showing 系の問題は検索するといろいろと出てくるのですが、今回の現象と同じものがすぐにはヒットせず、これはなんでもかんでもDirective
使ってるせいだな、と思って実際にionic
のコードを追いかけることにしました。原因は$ionicView
Controllerにありました。これはionic
で画面を作るときの、それぞれの画面の基盤となる<ion-view>
を管理してる人です。この人が初期化するときに
1 2 3 |
// add listeners for when this view changes $scope.$on('$ionicView.beforeEnter', self.beforeEnter); |
というように$ionicView.beforeEnter
イベントを監視しています。その名の通りViewを表示する直前に発火するイベントで、これがトリガとなってヘッダ周りを管理しているNavViewController
とかの処理が呼ばれます。最終的にHistory
を管理している$ionicHistory
とかの状態を見極めて、「戻る」ボタンを表示するかしないかを判定していることがわかりました。今回の事象をデバッグしようと思ってブレークポイントを貼ってみたんですけど、そもそもこの処理が走っていないことがわかりました。
ここで実装がまずそうな場所に気づいたんですけど、なんでもかんでもDirective
にしようと思った場合、ui-router
の定義で↓のような書き方をしています。
1 2 3 4 5 6 7 8 9 10 11 |
.state('tab.chat-detail', { url: '/chats/:chatId', views: { 'tab-chats': { - templateUrl: 'templates/chat-detail.html', - controller: 'ChatDetailCtrl' + template: '<chat-detail></chat-detail>' } } }) |
そして<chat-detail>
Directiveの定義は↓となっていて
1 2 3 4 5 6 7 8 9 10 11 |
.directive('chatDetail', function() { return { restrict: 'E', templateUrl: 'templates/chat-detail.html', controller: 'ChatDetailCtrl', controllerAs: 'vm', bindToController: true } }); |
さらにtemplate
は↓のようになっています。
1 2 3 4 5 6 7 8 9 |
<ion-view view-title="{{chat.name}}"> <ion-content class="padding"> <img ng-src="{{chat.face}}" style="width: 64px; height: 64px"> <p> {{chat.lastText}} </p> </ion-content> </ion-view> |
何がまずいかというと、<chat-detail>
Directiveの中に<ion-view>
Directiveが入ってしまっていることです。$ionicView.beforeEnter
イベントが発火するときには、まだ<ion-view>
が生成されていないんです。ってことはNavViewController
の処理も動かないので、ヘッダ周りの処理が何も動かないということになります。一度生成された後であれば、イベントの監視が可能になるので、2回めに表示された時に「戻る」ボタンが表示された理由も納得です。
対処法
じゃあどうすればいいのか、というとちょっと全ての可能性までは考えきれていないですけど、「Directive
の中に<ion-view>
を入れない」のが一番シンプルかと思います。<ion-view>
の 中身 をDirective
に入れるようにすればとりあえずOKです。
1 2 3 4 5 6 7 8 9 10 11 12 |
// ui-routerの定義 .state('tab.chat-detail', { url: '/chats/:chatId', views: { 'tab-chats': { - template: '<chat-detail></chat-detail>' + template: '<ion-view view-title="{{chat.name}}"><chat-detail></chat-detail></ion-view>' } } }) |
1 2 3 4 5 6 7 8 9 10 11 |
// template - <ion-view view-title="{{chat.name}}"> <ion-content class="padding"> <img ng-src="{{chat.face}}" style="width: 64px; height: 64px"> <p> {{chat.lastText}} </p> </ion-content> - </ion-view> |
すべてをDirective
にするときにはこういった初期化のタイミングの違いとかscope
の考え方には要注意ですね。ただ、今回の問題でionic
のフローには少し詳しくなれたのでちょっとレベルアップ感はあります(笑)