Posts cordova+ionicでAndroidアプリを(ちょっぴりtypescript、angularjs、gulp)
Post
Cancel

cordova+ionicでAndroidアプリを(ちょっぴりtypescript、angularjs、gulp)

背景

基本、Cordova+ionic+TypeScript+AngularJSでアプリ作成に書いてあることの確認作業です。この方もThe Ionic Bookにあるのを、TypeScript化したもののようです。

ただ、以下の理由で私もTODOアプリのメモ書きをブログにまとめました。

  • Cordova+ionic+TypeScript+AngularJSでアプリ作成の執筆者のkenfdevさんは、TypeScriptはWebStormに任せているようで、その辺をgulpに任せるようにしたこと。
  • わずか半年ちょっとですが、多少状況が変わっていて、一部情報が古くなっていること。
  • kenfdevさんは相当パワーユーザーのようで簡単なことははしょっているので、私が理解できずに調べたことなどを付け足したこと。
  • Windowsで実行したかった(IOSは無理なのでAndroidで確認する)。

というわけで、kenfdevさんには感謝しつつ、まとめてみました。

事前のインストール作業

※ Macの場合、npm -gでインストールするときはsudoが必要です。

  1. nodejsがなかったら、以下のURLに行って、インストールします。2015/10/15現在バージョンは4.2.1です。 https://nodejs.org/en/
  2. cordovaのインストール npm install -g cordova
  3. ionicのインストール npm install -g ionic
  4. android sdk のインストール
    • http://developer.android.com/sdk/index.html#OtherからAndroid SDK Managerをダウンロードし実行します。とりあえず6.0のSDKをインストール。
    • 環境変数にパスを通す。筆者の場合はC:\Users\chikkun\AppData\Local\Android\android-sdk\tools;C:\Users\chikkun\AppData\Local\Android\android-sdk\platform-tools
    • 環境変数にANDROID_SDK_HOMEを設定します。筆者の場合はANDROID_SDK_HOME=C:\Users\chikkun\AppData\Local\Android\android-sdkです。
  5. android avd で仮想デバイスを作成(とにかくなんでもOK)
  6. gulp系
  7. npm install -g gulp npm install gulp # gulp本体
    1. npm install gulp-util

      拡張子を変更するなどの便利メソッドが入っている。

      https://github.com/gulpjs/gulp-util

    2. npm install bower

      BowerはJavaScript,CSS,HTMLなどを依存関係を含めて管理してくれる、フロントエンド用パッケージ管理ツール。

    3. npm install gulp-concat

      ファイルを一つにまとめるもの

    4. npm install gulp-sass

      sassをコンパイルする

    5. npm install gulp-minify-css

      cssをminifyする

    6. npm install gulp-stylus

      stylusはsassの親戚(だから、2つは勉強するのはつらいので、いらないかも・・・)

    7. npm install gulp-rename

      ファイル名を変更する

    8. npm install shelljs

      jsでshell scriptを実行するもの?

    9. npm install gulp-sourcemaps # tyepescriptのsourcemapsを生成する
    10. npm install gulp-uglify   # JSをuglifyする
    11. npm install gulp-ng-annotate

      AngularJSでいちいち依存関係を文字列で表現したくないとき用いる、ようです(DI(Dependency Injection)依存性注入を参照))。

    12. npm install gulp-typescript

      typescriptのコンパイル用

    13. npm install run-sequence

      taskを順番通りに実行してもらう(typescript-compileタスクが終わってからjsタスクを行うため)

  8. typescriptとtypescriptの型定義ファイルを管理するためのtsdのインストール npm install -g typescript npm install -g tsd

実際のプロジェクトの作成

プロジェクトを作成

ionicコマンドでブランクのプロジェクトを作成します。

1
2
3
ionic start todo blank
cd todo
※今後はtodoディレクトリで作業します。

typescriptの型定義ファイルをインストール

  • tsd install cordova/* –save
  • tsd install cordova-ionic/* –save –override
  • tsd install angular –save –override

プラットフォームの追加

ionicコマンドで、IOSとAndroid、そしてブラウザでのでデバグ用にBrowserのプラットフォームを追加します(Windowsではiosのコンパイルは無理そうですが)。

1
2
3
ionic platform add ios
ionic platform add android --override
ionic platform add browser --override

stylディレクトリを作成

1
mkdir styl

必要無くなったら、後で消すことに。

gulp系

gulpファイルを置いておくディレクトリの作成

今回は、gulpfile.jsに全て書き込むのではなく、gulpディレクトリにあるjsファイルを読み込むような仕組みにします。

なので、ディレクトリを作成します。

1
mkdir gulp

css周り

gulpディレクトリーにcss.jsを作成し、次のような内容で保存する。

1
gulp/css.js
>
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
var gulp = require('gulp');
var sass = require('gulp-sass');
var stylus = require('gulp-stylus');
var minifyCss = require('gulp-minify-css');
var rename = require('gulp-rename');

gulp.task('styl', function(done) {
    gulp.src('styl/**/*.styl')
        .pipe(stylus())
        .pipe(gulp.dest('www/css'))
        .pipe(minifyCss({
            keepSpecialComments: 0
        }))
        .pipe(rename({
            extname: '.min.css'
        }))
        .pipe(gulp.dest('./www/css/'))
        .on('end', done);
});

gulp.task('sass', function(done) {
    gulp.src('./scss/ionic.app.scss')
        .pipe(sass())
        .pipe(gulp.dest('./www/css/'))
        .pipe(minifyCss({
            keepSpecialComments: 0
        }))
        .pipe(rename({
            extname: '.min.css'
        }))
        .pipe(gulp.dest('./www/css/'))
        .on('end', done);
});

gulp.task('css', ['sass', 'styl']);

gulp.task('watch:css', function() {
    gulp.watch('styl/**/*.styl', ['styl']);
    gulp.watch('scss/**/*.scss', ['sass']);
});
  1. 7行目はstylus系の処理で、minify化も行っています。
    1. gulp.srcで処理対象を指定して
    2. pipeで処理をつなげ
    3. stylusの処理を行い
    4. gulp.destで書き込み場所を指定し
    5. minifyCssでminifyし(コメントは全て削除で)、
    6. renameで、ファイル名を「〜.min.css」というものに変更して
    7. やはり、そのminifyファイルをgulp.destで書き込み場所を指定し
    8. 最後に渡されたdoneメソッドを実行して、終了します(以降は説明を省略)。
  2. 19行目からは、sass系の処理で、同様にminify化もしている。
  3. 31行目で、cssタスクとして、上記2つのタスクを登録しています。
  4. 32行目で、拡張子がstylとscssのファイルの変更を見張って、変更されたらcssタスクを実行します。

javascript周り

gulpディレクトリーにscript.jsを作成し、次のような内容で保存する。

1
gulp/scripts.js
>
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
var gulp = require('gulp');
var concat = require('gulp-concat');
var sourcemaps = require('gulp-sourcemaps');
var uglify = require('gulp-uglify');
var ngAnnotate = require('gulp-ng-annotate');

gulp.task('js', function() {
    gulp.src(['ng/module.js', 'ng/**/*.js'])
        .pipe(sourcemaps.init())
        .pipe(concat('app.js'))
        .pipe(ngAnnotate())
        .pipe(uglify({
            mangle: false
        }))
        .pipe(sourcemaps.write())
        .pipe(gulp.dest('www/js'))
});

gulp.task('watch', function() {
    gulp.watch('ng/**/*.js', ['js']);
    gulp.watch('ng/**/*.ts', ['typescript-compile']);
});

var tsc = require('gulp-typescript');

gulp.task('typescript-compile', function() {
    gulp.src(["ng/module.ts", "ng/**/*.ts"])
        .pipe(tsc({
            noImplicitAny: false,
            noEmitOnError: true,
            removeComments: false,
            sourceMap: true,
            target: "es5"
        }))
        .js
        .pipe(gulp.dest("ng/"));
});
  1. 7行目からjsタスクとして、以下の処理を登録
    1. 対照はng/module.jsとngディレクトリー以下にある拡張子がjsのファイルで、配列で渡すと順番通りに処理されるので、結果的にはmodule.jsが一番最初に処理されます
    2. sourcemapsを作るのに初期化 http://maruta.be/intfloat_staff/144を参考
    3. ファイルを全部app.jsにまとめ
    4. ng-annotateで配列アノテーションに変更し(ngAnnotateに付いてはDI(Dependency Injection)依存性注入を参照されたし)
    5. uglifyし(ローカル変数の短縮化を行わず)
    6. ソースマップを書き出し
    7. www/jsに書き出します。
  2. 19行目でngディレクトリの拡張子がjsとtsのファイルを監視し、変更されたら、それぞれこの上記jsタスクと下のtypescript-compileタスクを実行します。
  3. 26行目でtypescriptのコンパイルタスク(typescript-compile)を定義しています。
    1. ng/module.tsとng以下の拡張子がtsのファイルを対照に、この順番で処理させます。
    2. typescriptのコンパイルを行っています。
      1. any型などでも警告を出さない。
      2. 型チェックが通ったコードしか出力されない。
      3. コメントは取らない。
      4. ソースマップを作成する。
      5. EcmaScript5にする。
      6. ngディレクトリに書き出す。

gulpfile.js

すでにionicによってtodoディレクトリにできているようですが、gulpfile.jsを次のような内容で上書きします。

>
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
var gulp = require('gulp');
var gutil = require('gulp-util');
var bower = require('bower');
var concat = require('gulp-concat');
var sass = require('gulp-sass');
var minifyCss = require('gulp-minify-css');
var rename = require('gulp-rename');
var sh = require('shelljs');

var fs = require('fs');

fs.readdirSync(__dirname + '/gulp').forEach(function(task) {
    require('./gulp/' + task)
});

var runSequence = require('run-sequence');

gulp.task('default', function(callback) {
    return runSequence(
        'typescript-compile',
        'css',
        'js',
        callback
    );
});
  1. 12行目で、gulpディレクトリのjsファイルを読み込むようにする。
  2. 18行目で、defaultのタスクを定義している。
  3. run-sequenceを利用して、必ずtypescript-compileタスクを行ってから、jsタスクが行われるようにする。

htmlファイル

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
  <title>TODO</title>

  <link href="lib/ionic/css/ionic.css" rel="stylesheet">
  <link href="css/ionic.app.css" rel="stylesheet">
  <link href="css/style.css" rel="stylesheet">


  <!-- ionic/angularjs js -->
  <script src="lib/ionic/js/ionic.bundle.js"></script>

  <!-- cordova script (this will be a 404 during development) -->
  <script src="cordova.js"></script>
  <!-- <script src="lib/angular/angular.min.js"></script>-->

  <!-- your app's js -->
  <script src="js/app.js"></script>
</head>

<body ng-app="todo" ng-controller="TodoCtrl as vm">
  <ion-side-menus>
    <!-- Center content -->
    <ion-side-menu-content>
      <ion-header-bar class="bar-dark">
        <button class="button button-icon" ng-click="vm.toggleProjects()">
          <i class="icon ion-navicon"></i>
        </button>
         <h1 class="title">{{vm.activeProject.title}}</h1>
        <button class="button button-icon" ng-click="vm.newTask()">
          <i class="icon ion-compose"></i>
        </button>
      </ion-header-bar>
      <ion-content scroll="false">
        <ion-list>
          <ion-item ng-repeat="task in vm.activeProject.tasks">
            {{task.title}}
          </ion-item>
        </ion-list>
      </ion-content>
    </ion-side-menu-content>

    <!-- Left menu -->
    <ion-side-menu side="left">
      <ion-header-bar class="bar-dark">
        <h1 class="title">Projects</h1>
        <button class="button button-icon ion-plus" ng-click="vm.newProject()">
        </button>
      </ion-header-bar>
      <ion-content scroll="false">
        <ion-list>
          <ion-item ng-repeat="project in vm.projects" ng-click="vm.selectProject(project, $index)" ng-class="{active: vm.activeProject == project}">
            {{project.title}}
          </ion-item>
        </ion-list>
      </ion-content>
    </ion-side-menu>
  </ion-side-menus>

  <script id="new-task.html" type="text/ng-template">
    <div class="modal">
      <!-- Modal header bar -->
      <ion-header-bar class="bar-secondary">
        <h1 class="title">New Task</h1>
        <button class="button button-clear button-positive" ng-click="vm.closeNewTask()">Cancel</button>
      </ion-header-bar>

      <!-- Modal content area -->
      <ion-content>
        <form ng-submit="vm.createTask(task)">
          <div class="list">
            <label class="item item-input">
              <input type="text" placeholder="What do you need to do?" ng-model="task.title">
            </label>
          </div>
          <div class="padding">
            <button type="submit" class="button button-block button-positive">Create Task</button>
          </div>
        </form>
      </ion-content>
    </div>
  </script>
</body>

</html>

少々補足

  1. ざっくり言うと、下の図のような機能を持つアプリです。 ブラウザで見た画面
  2. 8行目はionicのスタイルファイルです。
  3. 9行目は1をカスタマイズする場合のスタイルファイルです。
  4. 10行目は、必要に応じて足していく、今回特有のスタイルファイルです。
  5. 14行目は、冒頭に次のようなコメントが付いているので、angular系も含めて集めているJavaScriptファイルのようです。 /*! * ionic.bundle.js is a concatenation of: * ionic.js, angular.js, angular-animate.js, * angular-sanitize.js, angular-ui-router.js, * and ionic-angular.js */
  6. 17行目のcordova.jsは、html内のコメントにもあるように、ブラウザベースの時は読み込めないようで、実機とかに入れたときは読み込むようです。Cordovaの本体なんですかね。
  7. 18行目はコメントアウトしていますが、これは上記4の理由からでしょう。
  8. 21行目は今回作成していくapp.jsです。
  9. 23行目はbodyタグにng-appng-controllerを仕込んでいて、TodoCtrlというコントローラ(vmで参照できる)がこのbodyタグ以下(実質全部)を取り扱っていくようにしています。
  10. 28行目から45行目は下の図のBのサイドメニューにあたります。 ```html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[ion-side-menus参照](http://ionicframework.com/docs/api/directive/ionSideMenus/)

[ion-side-menu-content参照](http://ionicframework.com/docs/api/directive/ionSideMenuContent/)

10. 47行目〜61行目

```html
<!-- プロジェクトを管理する左側 -->
<ion-side-menu side="left">
  <ion-header-bar class="bar-dark">
    <h1 class="title">Projects</h1>
    <button class="button button-icon ion-plus" ng-click="vm.newProject()"><!-- プラスのアイコン  -->
    </button>
  </ion-header-bar>
  <ion-content scroll="false">
    <ion-list>
	  <!-- プロジェクトを選択するところ。TodoCtrlのselectProjectが実行される -->
      <ion-item ng-repeat="project in vm.projects" ng-click="vm.selectProject(project, $index)" ng-class="{active: vm.activeProject == project}">
            
      </ion-item>
    </ion-list>
  </ion-content>
</ion-side-menu>
  1. 64行目〜86行目 これは新規Todoを作成するときに上がるポップアップのテンプレート。

TypeScriptファイル

ng/module.ts

>
1
2
3
4
5
6
7
8
9
10
11
12
/// <reference path='../typings/tsd.d.ts' />
angular.module('todo', ['ionic'])
    .run(function($ionicPlatform) {
    $ionicPlatform.ready(function() {
      if(window.cordova && window.cordova.plugins.Keyboard) {
        cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
      }
      if(window.StatusBar) {
        window.StatusBar.styleDefault();
      }
    });
  })
  1. 1行目ではtsdでインストールしたtypescriptの定義ファイルをインクルードしている。
  2. 2行目からはionicでangularを使う場合のお決まり。実際最初ionic start todo blankでプロジェクトを作った際にtodo/www/js/app.jsに書いてあるのを、ほぼそのまま(1行目を加えているのと、コメントを取っているだけ)です。

ng/TodoCtrl.ts

だいぶ長いけれど、これが本体なので、じっくり見て下さい。可能な限りコメントを入れています。

>
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/// <reference path='../typings/tsd.d.ts' />
/// <reference path='ProjectsSvc.ts' />
//↑はtypescriptの型を解決するためのもので、後で出てくるProjectsSvc.tsも参照している
// namespaceをtodoに。
namespace todo {
  "use strict";

  // Todoの内容を入れるオブジェクト(interface)
  // typescriptのinterfaceは、タイトルというキーには文字が入るんだという、
  // taskのデータ型を宣言しているだけで、実際のJavascriptではこの部分はコードにならない(みたい)
  export interface ITask {
    title: string;
  }

  // コントローラ用のクラス、今回の中心。
  class TodoCtrl {
    // プロジェクトを入れておくIProjectの配列(IProject自体はProjectsSvc.ts)
    projects: IProject[];
	// TODOを登録する際の対照のプロジェクト
    activeProject: IProject;
	// taskModal?
    taskModal: any;

    // modal用の$ionicModalの注入とそのionicModalが$scopeを
    // 必要とするので$scopeを注入
    constructor(private $scope, private $timeout: ng.ITimeoutService, private $ionicModal, private Projects: ProjectsSvc, private $ionicSideMenuDelegate) {
      // 既存のプロジェクト読み込み又は初期化(localStrageにあったら、そこから拾う)
      this.projects = Projects.all();

      // 最後に選択していたプロジェクトか、なければ最初のプロジェクトを選択しておく↓
	  // return parseInt(window.localStorage['lastActiveProject']) || 0;
      this.activeProject = this.projects[Projects.getLastActiveIndex()];

      // Create and load the Modal
      $ionicModal.fromTemplateUrl('new-task.html', {//new-task.htmlはscriptタグのid='new-task.html'
        scope: this.$scope
      }).then((modal) => {
        // ここでthisを使いたい場合は要注意。TodoCtrlを
        // 示したいので必ず()=>{}で関数を書かないといけない!
		// 参考:http://www.buildinsider.net/language/quicktypescript/01#arrow-function-expressions
		// 以下のように変換されている
		// var _this = this;
		// ...
		// _this.taskModal = modal;
		// 参考:http://ionicframework.com/docs/api/service/$ionicModal/
		// modalはhideやshowできる
		// scriptタグの中にあるテンプレートの中にmodalの中身がある
        this.taskModal = modal;
      });

      // プロジェクトが存在しない場合は
      // 作ってもらう。初期化が全て完了してから
      // 実行してほしいので$timeoutで遅延実行する。
      this.$timeout(() => {
          if (this.projects.length > 0) {
            // プロジェクトが既にあるなら無視
            return;
          }
          while (true) {
            var projectTitle = prompt('最初のプロジェクト名を指定:');
            if (!!projectTitle) {
              this.createProject(projectTitle);
              break;
            }
          }
        }
      )
    }//コンストラクターの終了

    // プロジェクトの新規作成(非公開)
	// ↑の$timeoutの引数の関数の中で実行している
    private createProject(projectTitle: string) {
      var newProject = this.Projects.newProject(projectTitle);
	  //新しいプロジェクトを配列にpushし
      this.projects.push(newProject);
      // プロジェクト新規作成後すぐにアクティブ状態にしておきたいので設定
      this.activeProject = newProject;
      this.Projects.save(this.projects);
    }

    // プロジェクト新規作成(公開)
	// プラスのアイコンをクリックするとこのメソッドが実行される
    newProject() {
      var projectTitle = prompt('Project name');
      if (!!projectTitle) {
        this.createProject(projectTitle);
      }
    }

    // プロジェクトの選択
	// 左側のプロジェクト名をクリックすると、このメソッドが実行される
    selectProject(project: IProject, index: number) {
      this.activeProject = project;
      this.Projects.setLastActiveIndex(index);
	  // 左側を見えないようにする
	  // 参考:http://ionicframework.com/docs/api/service/$ionicSideMenuDelegate/
      this.$ionicSideMenuDelegate.toggleLeft(false);
    }

    // フォームのsubmit時のタスク作成
    createTask(task: ITask) {
      if (!this.activeProject || !task) {
        // タスク作成できるような状態じゃなかったら無視
        return;
      }

      this.activeProject.tasks.push({
        title: task.title
      });
      this.taskModal.hide();

      // タスク作成するたびに前プロジェクト保存しているので
      // 非効率的だけどチュートリアルなので…
      this.Projects.save(this.projects);
      task.title = "";
    }

    // タスク作成のmodalを表示
    newTask() {
      this.taskModal.show();
    }

    // タスク作成modalを閉じる
    closeNewTask() {
      this.taskModal.hide();
    }

    // サイドメニューのトグル用
    toggleProjects() {
      this.$ionicSideMenuDelegate.toggleLeft();
    }
  }

  // todoモジュールにちゃんと定義
  angular.module('todo').controller('TodoCtrl', TodoCtrl);
}

※ngAnnotationの機能により、変換されたJavascriptにおいて

1
TodoCtrl.$inject = ["$scope", "$timeout", "$ionicModal", "Projects", "$ionicSideMenuDelegate"];

が挿入されている(たぶん)。

ng/ProjectsSvc.ts

>
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
/// <reference path='TodoCtrl.ts' />
/// <reference path='../typings/tsd.d.ts' />
namespace todo {
  "use strict";


  // プロジェクト用のインタフェース
  export interface IProject {
    title: string;
    tasks: ITask[];
  }

  // Projectsサービスクラス
  // exportすることにより、TodoCtrl内で利用できるようになる。
  // 次のように一番上のスコープのtodoに登録される
  // todo.ProjectsSvc = ProjectsSvc;
  export class ProjectsSvc {
    // 管理している全プロジェクト一覧を取得
    all():IProject[] {
      var projectString = window.localStorage['projects'];
      // !!は「projectString != null ? true : false」と同じ
      if(!!projectString) {
        return angular.fromJson(projectString);
      }
      //storageに何もなかったら空の配列を
      return [];
    }

    // 管理している全プロジェクトをlocalStorageに保存
    save(projects: IProject[]) {
      window.localStorage['projects'] = angular.toJson(projects);
    }


    // 新規プロジェクトの作成
    newProject(projectTitle: string): IProject {
      return {
        title: projectTitle,
        tasks: []
      };
    }

    // 最後に選択していたプロジェクトの索引取得
    getLastActiveIndex() {
      return parseInt(window.localStorage['lastActiveProject']) || 0;
    }

    // 最後に選択していたプロジェクトの索引保存
    setLastActiveIndex(index: number) {
      window.localStorage['lastActiveProject'] = index;
    }
  }

  // Projectsサービスとして登録
  angular.module('todo').factory('Projects', () => {
    return new ProjectsSvc();
  });
}

いよいよコンパイルと実行

typescriptをコンパイルしてapp.jsに、CSSも

1
gulp

andoridをコンパイルして、確認

1
2
3
4
ionic build android
ionic emulate android
# androidが立ち上がるのに時間がかかるので以下の方が良いかも
ionic serve

ng-controllerをDirectiveに変更を試みる

Angularのお勉強、ちょっぴり

DI(Dependency Injection)依存性注入

JavaのSpring等で言われるDIなんだけれど、ちょっぴりイメージが違います。というのもJavaScriptではそもそも型がないから、AngularJSは、Javaの時のようにInterfaceで型を統一させておいて、設定ファイル(Annotationでも可能ですが、これまた、AngularJSのAnnotationと違った意味なので混乱してしまう・・・)で、DIしてもらう、というような感じではありません。

AngularJSではcontrollerで利用するオブジェクトなんかを次の3つの方法でDIをしてもらうようになっています。

  1. functionのパラメータ名による暗黙的なアノテーション
  2. $injectプロパティを利用したアノテーション
  3. inline arrayを利用したアノテーション
1
2
3
4
angular.module('myApp', [])
  .controller('MyControler', function($scope){
    $scope.msg = 'Hello World, AngularJS!';
});

このfunction($scope)$scopeと引き数名に$scopeと書いておくと、AngularJSが実行時に「ああなるほど、このコントローラでは$scopeが必要なんだね。だったら注入してあげるよ」と自動で注入してくれる、という仕組みが上記の1です。

しかしながらこの方法は、JavaScriptをminifyしたときに使えなくなります(変数名を短くしてしまうから)。なので、一見便利ですが、使えないというのが現実です。

上記2は

1
MyController['$inject'] = ['$scope', 'greeter'];

のように手動で注入する方法です。そして、3は配列アノテーションとして指定する方法です。

1
2
3
4
angular.module('myApp', [])
  .controller('MyControler', ['$scope', 'BookList'], function($scope, BookList){
    $scope.books = BookList();
});

の「[‘$scope’, ‘BookList’]」で文字列として渡す方法です。この場合ですと、$scopeとBookListを注入してくれ、とAngularJSに指示しているわけです。通常はこれを使うのが一般のようです。

ng-annotate

[‘$scope’, ‘BookList’], function($scope, BookList){のように2回同じ名前を書かなくてはならず面倒なので、それを自動で行ってしまえ、というツールがng-annotateです。

例えば、

1
2
3
4
angular.module('myApp', [])
  .controller('MyControler', function($scope, BookList){
     $scope.books = BookList();;
});

と書いてあるtmp.jsに対して、

1
 ng-annotate -ar tmp.js

と実行すると、次のようなものが標準出力されます。

1
2
3
4
angular.module('myApp', [])
  .controller('MyControler', ["$scope", "BookList", function($scope, BookList){
     $scope.books = BookList();
}]);

これはgulp-ng-annotateを利用すると、自動化できるというわけです。ただし、上記の2のパターンのようです(今回の例では、下のような1行が追加されます)。

1
    TodoCtrl.$inject = ["$scope", "$timeout", "$ionicModal", "Projects", "$ionicSideMenuDelegate"];

ちにみに、インストールはいつもの

1
 npm -g install ng-annotate

です。

蛇足

TypeScriptのpygments lexer

PythonのPygmentsで、コードのハイライトを作成していますが、TypeScriptのLexerはありません。ちょっと検索したら、あったので、以下のURLを利用して、インストールしました。

https://www.snip2code.com/Snippet/108735/typescript-lexer-from-https---bitbucket-

Custom Lexer Setup方法

http://www.catchmecode.com/2013/03/custom-syntax-in-pygments.html

http://www.yegor256.com/2014/06/29/custom-lexer-in-jekyll.html

cordova 情報

IOS

Android

Please install Android target: “android-xxが出た時の対策

シェア
#内容発言者

WordPress ウィジェット を作る

Object.defineProperty〜TypeScriptのDecoratorまで

坂井和郎