初投稿です。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のフローには少し詳しくなれたのでちょっとレベルアップ感はあります(笑)
