この記事は ionic + TypeScriptで今風な開発フローを実現 の第4回にあたります!他の記事は↓です。
- ionic + TypeScript で今風な開発フローを実現【導入編】
- ionic + TypeScript で今風な開発フローを実現【型定義ファイル活用編】
- ionic + TypeScript で今風な開発フローを実現【module, concat編】
- ionic + TypeScript で今風な開発フローを実現【sourcemap編】
- ionic + TypeScript で今風な開発フローを実現【TypeScript編】
いざ、ES2015(旧ES6)の世界へ
コンパイルエラーがなくなったところで、本格的に TypeScript を書いていきましょう!ここでよく勘違いされる点ですが、 TypeScriptの文法が難しい とか聞きます。これはたいてい ES2015の文法が難しい っていうのが正しい言い方です。
ES2015???
ES2015って??? 初めて聞いた人もいるかもしれませんが、JavaScriptの正式名称はECMAScript です。これのバージョンで、執筆時点での世間的に一番ブラウザで実装されているのが ES5 です。最新版が ES2015(元々はES6と呼ばれていた) となります。この ES2015 になってJavaScriptの文法は 結構な割合 で変わります。その文法の違いについて一つ一つ見て行ったらきりがないので、 ES2015
で検索して色々な良記事を読んで下さい。
何が言いたかったかと言うと、 TypeScript は ES2015 を先取りして書くことができるんです!というかほとんどの場合 ES2015 で書くことになると思います(個人的にコードの量減るし、読みやすいしで圧倒的に ES2015 のほうが好きです)。最終的に transpile 時に ES5 とかに落とし込んでくれるんです。 TypeScript のサンプルコードとかも ES2015 で書かれているので、この文法=TypeScript と勘違いする人が多くて、「TypeScriptは文法が難しい」と言う人がちらほらいます。確かに結構文法は変わるんですけど、この先この文法が主流になることは確定しているので、学んでおいて損は無いです!
それでは、前回までのコードを ES2015 に移行しましょう!
モジュール
Controllerの分割
ES2015 から個々に分散したJSファイルを import
で読み込むことができます。まずは controllers
を分割しましょう!
DashCtrl
, ChatsCtrl
, ChatDetailCtrl
, AccountCtrl
の4つのコントローラがあるので、それぞれを個々のファイルに分割しましょう!また、分割したファイルは controllers
フォルダに入れることにします!
1 2 3 4 5 |
// controllers/dash.controller.ts // DashCtrl、中身ないけど一応分割 export default function DashCtrl($scope) { } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// controllers/chats.controller.ts export default function ChatsCtrl($scope, Chats) { // With the new view caching in Ionic, Controllers are only called // when they are recreated or on app start, instead of every page change. // To listen for when this page is active (for example, to refresh data), // listen for the $ionicView.enter event: // //$scope.$on('$ionicView.enter', function(e) { //}); $scope.chats = Chats.all(); $scope.remove = function(chat) { Chats.remove(chat); }; } |
1 2 3 4 5 |
// controllers/chatDetail.controller.ts export default function ChatDetailCtrl($scope, $stateParams, Chats) { $scope.chat = Chats.get($stateParams.chatId); } |
1 2 3 4 5 6 7 |
// controllers/account.controller.ts export default function AccountCtrl($scope) { $scope.settings = { enableFriends: true }; } |
それでは、元々の controllers.ts
も controllers
フォルダに入れて、名前もわかりやすいように controllers.module.ts
に変えちゃいましょう!別のファイルから読み込むときの import <インポートするもの> from '<ファイルパス>'
がポイントです。
1 2 3 4 5 6 7 8 9 10 11 12 |
// controller/controllers.module.ts import DashCtrl from './dash.controller'; import ChatsCtrl from './chats.controller'; import ChatDetailCtrl from './chatDetail.controller'; import AccountCtrl from './account.controller'; angular.module('starter.controllers', []) .controller('DashCtrl', DashCtrl) .controller('ChatsCtrl', ChatsCtrl) .controller('ChatDetailCtrl', ChatDetailCtrl) .controller('AccountCtrl', AccountCtrl); |
どうでしょう?結構すっきりしたと思いませんか!?
Servicesの分割
同様に services
も変換しちゃいましょう!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
// services/chats.service.ts export default function ChatsService() { // Might use a resource here that returns a JSON array // Some fake testing data var chats = [ { id: 0, name: 'Ben Sparrow', lastText: 'You on your way?', face: 'img/ben.png' }, { id: 1, name: 'Max Lynx', lastText: 'Hey, it\'s me', face: 'img/max.png' }, { id: 2, name: 'Adam Bradleyson', lastText: 'I should buy a boat', face: 'img/adam.jpg' }, { id: 3, name: 'Perry Governor', lastText: 'Look at my mukluks!', face: 'img/perry.png' }, { id: 4, name: 'Mike Harrington', lastText: 'This is wicked good ice cream.', face: 'img/mike.png' } ]; return { all: function() { return chats; }, remove: function(chat) { chats.splice(chats.indexOf(chat), 1); }, get: function(chatId) { for (var i = 0; i < chats.length; i++) { if (chats[i].id === parseInt(chatId)) { return chats[i]; } } return null; } }; } |
1 2 3 4 5 6 |
// services/services.module.ts import ChatsService from './chats.service'; angular.module('starter.services', []) .factory('Chats', ChatsService); |
appの分割
最後に app.ts
も整理しましょう!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// app.run.ts export default function runBlock($ionicPlatform) { $ionicPlatform.ready(function() { // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard // for form inputs) if (window.cordova && window.cordova.plugins && window.cordova.plugins.Keyboard) { cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true); cordova.plugins.Keyboard.disableScroll(true); } if (window.StatusBar) { // org.apache.cordova.statusbar required StatusBar.styleDefault(); } }); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
// app.config.ts export default function appConfig($stateProvider, $urlRouterProvider) { // Ionic uses AngularUI Router which uses the concept of states // Learn more here: https://github.com/angular-ui/ui-router // Set up the various states which the app can be in. // Each state's controller can be found in controllers.js $stateProvider // setup an abstract state for the tabs directive .state('tab', { url: '/tab', abstract: true, templateUrl: 'templates/tabs.html' }) // Each tab has its own nav history stack: .state('tab.dash', { url: '/dash', views: { 'tab-dash': { templateUrl: 'templates/tab-dash.html', controller: 'DashCtrl' } } }) .state('tab.chats', { url: '/chats', views: { 'tab-chats': { templateUrl: 'templates/tab-chats.html', controller: 'ChatsCtrl' } } }) .state('tab.chat-detail', { url: '/chats/:chatId', views: { 'tab-chats': { templateUrl: 'templates/chat-detail.html', controller: 'ChatDetailCtrl' } } }) .state('tab.account', { url: '/account', views: { 'tab-account': { templateUrl: 'templates/tab-account.html', controller: 'AccountCtrl' } } }); // if none of the above states are matched, use this as the fallback $urlRouterProvider.otherwise('/tab/dash'); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// app.module.ts import runBlock from './app.run'; import appConfig from './app.config'; // Ionic Starter App // angular.module is a global place for creating, registering and retrieving Angular modules // 'starter' is the name of this angular module example (also set in a <body> attribute in index.html) // the 2nd parameter is an array of 'requires' // 'starter.services' is found in services.js // 'starter.controllers' is found in controllers.js angular.module('starter', ['ionic', 'starter.controllers', 'starter.services']) .run(runBlock) .config(appConfig); |
webpackデグレ確認
フォルダ構成がいい感じになってきました〜。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
src └── app ├── app.config.ts ├── app.module.ts ├── app.run.ts ├── controllers │ ├── account.controller.ts │ ├── chatDetail.controller.ts │ ├── chats.controller.ts │ ├── controllers.module.ts │ └── dash.controller.ts └── services ├── chats.service.ts └── services.module.ts |
さてさて、ちゃんとアプリケーションが動作するかチェックしましょう!ちょっとパスが変わったので、 webpack
の設定も修正します。
1 2 3 4 5 6 7 |
// webpack/webpack.dev.js entry: { app: './src/app/app.module.ts', controllers: './src/app/controllers/controllers.module.ts', services: './src/app/services/services.module.ts' } |
ビルドして
1 2 |
npm run watch |
起動!
1 2 |
ionic serve |
ちゃんと起動確認ができたはずです!
concat
小さなモジュールに分けたのは良いんですが、これらを一つのファイルにたいてい concat
するのが今の主流です(厳密にはちょっと小分けしますが)。
それでは、一番てっとり早い方法として app.module.ts
に controllers.module.ts
と services.module.ts
を import
して app.module.ts
の依存関係にしちゃいましょう!こうすることで webpack
がいい感じにこれらのファイルを一つのファイルに合体してくれます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// app.module.ts // moduleをインポート import './controllers/controllers.module.ts'; import './services/services.module.ts'; import runBlock from './app.run'; import appConfig from './app.config'; // Ionic Starter App // angular.module is a global place for creating, registering and retrieving Angular modules // 'starter' is the name of this angular module example (also set in a <body> attribute in index.html) // the 2nd parameter is an array of 'requires' // 'starter.services' is found in services.js // 'starter.controllers' is found in controllers.js angular.module('starter', ['ionic', 'starter.controllers', 'starter.services']) .run(runBlock) .config(appConfig); |
最後に、 webpack/webpack.dev.js
のエントリポイントを一つにします。
1 2 3 4 |
entry: { app: './src/app/app.module.ts' } |
1 2 3 4 5 6 7 8 9 10 11 |
// ビルド! npm run watch // app.js 1ファイルになりました! Hash: 9490521a9f34620df80c Version: webpack 1.12.14 Time: 1914ms Asset Size Chunks Chunk Names app.js 8.45 kB 0 [emitted] app + 10 hidden modules |
起動確認!
1 2 |
ionic serve |
またまたこれで今風な開発スタイルに近づきました。わくわくするのは私だけでしょうか?笑
次回はさらに ES2015 使ったり、 concat
したファイルをデバッグするにはどうしたらいいかなど、見ていきましょう!
- [x] コンパイルエラーが出ている
- [x] コンパイル後のファイルが複数ある
- [ ] ブラウザでデバッグしづらい
- [ ] 型をまるで使っていない