AngularJSのモジュール
最初に学ぶべき概念は「モジュール」である。
AngularJSでは、モジュールによって、関連するコードを1つにパッケージする。
AngularJSのモジュールには2つの部品がある。
- モジュールは自身のコントローラー、サービス、ファクトリ、ディレクティブを定義することができる
- これらにはモジュールを通してアクセスできる
- モジュールは別のモジュールに依存することができる
- モジュールの初期化処理において依存モジュールを指定する
モジュールは、関連するJavaScriptの容れ物であるのと同時に、AngularJSがアプリケーションを開始するのに使用するものでもある。
ng-appディレクティブで、アプリケーションのエントリポイントを指定できる。
var app = angular.module('myApp', []); // myAppモジュール初期化
app = angular.module('myApp'); // myAppモジュールを取得
angular.moduleメソッドの第1引数はモジュール名、第2引数は依存しているモジュール名の配列になる。
第2引数を渡さない場合、myAppという名前のモジュールを探して返す。
コントローラーの作成
AngularJSにおけるコントローラーの役割は以下のようになる。
- サーバから現在のUIに合わせたデータを取得する
- ユーザにどのデータを見せるか決める
- 表示のロジック
- ユーザーとのやりとり
UIに関係のないコントローラーは存在しない(UIに関係ないビジネスロジックはサービスに書くべき)。
コントローラーは以下のように定義する。
angular.module('myApp', [])
.controller('MainCtrl', ['$scope', function($scope) {
$scope.greeting = 'hello';
}]);
AngularJSのモジュールのcontrollerメソッドを使って、コントローラーを定義することができる。
controllerメソッドは第1引数にコントローラー名、第2引数に配列を取る。
第2引数の配列には、依存しているモジュール名の文字列を入れ、配列の最後の要素にコントローラーとなる関数を入れる。
(依存モジュールが無い場合は関数だけを入れる)
なお、第2引数には配列でなく関数を渡すこともできるが、JavaScriptコードを圧縮すると動作しなくなるため非推奨。
// 非推奨の書き方
angular.module('myApp', [])
.controller('MainCtrl', function($scope) {
$scope.greeting = 'hello';
});
また、従来のサンプルコードでよく使われていた、グローバルに関数を定義してコントローラーとする方法は、1.3以降では動かない。
ng-controllerディレクティブを使うことで、コントローラーを指定できる。
<html ng-app='myApp'>
<head>
<script src='https://ajax.googleapis.com/ajax/libs/angularjs/1.2.19/angular.js'>
</script><script>
angular.module('myApp', [])
.controller('MainCtrl', [function () {
this.helloMsg = 'Hello';
}]);
</script>
</head>
<body ng-controller='MainCtrl as ctrl'>
{{ctrl.helloMsg}}
</body>
</html>
ControllerAs記法はAngularJS1.2で導入された機能で、コントローラー自身のプロパティにビューからアクセスできるようにすることができる。
上記サンプルでは、ng-controllerのControllerAs記法でMainCtrlに別名をつけ、MainCtrlに定義されたプロパティにアクセスしている。
コントローラーがネストされた複雑なUIでは、ビューの変数がどこから来ているか判別するのが難しいことがある。
ControllerAs記法を使うと、コントローラーの名前によって変数の出処がはっきりする。
配列を扱う
ng-repeatディレクティブを使うことで、配列を表示することができる。
angular.module('myApp', [])
.controller('MainCtrl', [function() {
var self = this;
self.notes = [
{id: 1, label: 'first'},
{id: 2, label: 'second'},
{id: 3, label: 'third'},
];
}]);
ng-repeatディレクティブを使って、MainCtrl.notesを表示するコードは以下。
<div ng-repeat="note in ctrl.notes">
<span class="label" ng-bind="::note.label"></span>
</div>
ここで、ng-repeatの付いた要素と、その中身の要素は、繰り返しの際のテンプレートになる。
上記コードの場合、中にspanを含むdivが3つ生成される。
ここでは、ビューで{{}}の代わりにng-bindディレクティブを使っている。
{{}}を使うと、AngularJSアプリケーションの読み込みが完了するまで{{}}が表示されてしまうことがあるが、ng-bindではこの問題を回避できる。
この問題は、ng-cloakディレクティブを使うことでも回避可能である。
AngularJSのロードが完了するのを待つ
angular.js(又はangular.min.js)を読み込むと、以下のCSSが自動的に定義される。
[ng:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
display: none !important;
}
[ng-cloak]は、ng-cloakという属性を持つ要素に対するセレクタなので、このCSSルールでは、ng-cloak属性のついた要素を非表示にする。
ロードが完了すると、AngularJSはng-cloak属性を要素から取り除くため、要素が画面上に表示される。
bodyタグにng-cloakをつけることもできるが、細かなパーツごとにつけていった方がユーザビリティが良い。
angular.jsのソースコードの読み込みが完了するまで、上記CSSは定義されていないことに注意が必要。
※一般に、CSSは先に、JavaScriptは後に読み込むほうがページ表示のパフォーマンスが良くなる。
上記CSSルールを自前のCSSファイルの中で定義しておくことで、より確実に非表示にすることができる。
AngularJSの処理の流れ
- HTMLの読み込みが完了し、JavaScriptの読み込みが始まる
- ドキュメント全体の読み込みが完了し、AngularJSの動作が始まる
- ng-appディレクティブを見つけると、関連するモジュールを探して読み込む
- ng-app配下のDOMを探索し、ディレクティブやバインド文を探す
- ng-controller又はng-repeatディレクティブを見つけたら、その都度scopeを作成する
- HTMLからアクセスされる変数にウォッチャーとリスナーを登録する(2way-binding)
- AngularJSはモデルのデータが変わった場合にのみ、UIの更新を行う
ワンタイムバインディングによるngBindの最適化
AngularJS1.3以上では、ワンタイムバインディングを利用することでngBindのパフォーマンスを最適化できる。
以下のようなデータがあると想定する。
// コントローラーの定義内
this.notes = [
{id: 1, label: 'first'},
{id: 2, label: 'second'},
{id: 3, label: 'third'},
];
通常のngBindと、ワンタイムバインドを使った構文の例は以下。
<div ng-repeat="note in ctrl.notes">
Two-way binding: <div ng-bind="note.label">
Onet-time binding: <div ng-bind="::note.label">
</div>
ワンタイムバインディングの方では、変数の前に「::」をつけている。
この記号をつけると、変数は初回表示の時にだけ使用され、その後更新はされなくなる。
ページ内で双方向バインディングされている変数の数が多くなるほど、更新処理のオーバーヘッドは大きくなる。
ワンタイムバインディングを使うことで、更新処理のオーバーヘッドを最小化することができる。
その他のディレクティブ
ng-show/ng-hide
ng-show/ng-hideディレクティブを使うと、要素の表示非表示を制御することができる。
<span ng-show="true">show when true</span>
<span ng-hide="true">hide when true</span>
ng-show/hideの条件には式を渡すこともできる。
<span ng-show="ctrl.returnTrueFunc()">show when true</span>
ng-show/hideで真として評価される値は、Booleanのtrue、空白でない文字列、0でない数値、nullでないJavaScriptのオブジェクトである。
ng-class
ng-classディレクティブはCSSクラスを付け外しするのに使用される。
ng-classディレクティブには、適用するクラスを文字列として渡す使い方と、オブジェクトを渡す使い方がある。
<span ng-class="warning">.warning is applied</span>
<span ng-class="{'success': false, 'warning': true}">.warning is applied</span>
オブジェクトを渡す方法の場合、渡すオブジェクトの値がtrueである要素のキーがクラスとして適用される。
実際のコードでは以下のような実装になる。
// コントローラーの定義
this.getClass = function(status) {
return {
done: status,
penging: !status
}
};
以下はビュー。itemという変数にstatusというプロパティがあるとする。
<span ng-class="ctrl.getClass(item.status)">item.status</span>
上記コードの場合、item.statusがtrueであれば.doneが、falseであれば.pendingが適用される。
ng-repeatを扱う
ng-repeatは繰り返し行うディレクティブである。
オブジェクトをng-repeatする
以下のようなオブジェクトがあるとする。
self.notes = {
shyam: {
id: 1,
label: 'First Note',
done: false
},
Misko: {
id: 3,
label: 'Finished Third Note',
done: true
},
brad: {
id: 2,
label: 'Second Note',
done: false
}
};
このオブジェクトをng-repeatで回す場合は、ビューを以下のように書く。
<div ng-repeat="(author, note) in ctrl.notes">
<span class="label"> {{note.label}}</span>
<span class="author" ng-bind="author"></span>
</div>
ng-repeatはオブジェクトをループする場合、アルファベット順で、大文字の方が先になる。そのため、上の例ではMisko, brad, shyamの順に表示される。
ng-repeatは、value in Array又は(key, value) in Objectという形をとる。配列の場合には、ng-repeatは配列の先頭から順に表示する。
ng-repeatのヘルパー変数
ng-repeatには、HTMLテンプレートで利用できるヘルパー変数が存在する。
<div ng-repeat="note in ctrl.notes"> <div>First Element: {{$first}}</div> <div>Middle Element: {{$middle}}</div> <div>Last Element: {{$last}}</div> <div>Index of Element: {{$index}}</div> <div>At Even Position: {{$even}}</div> <div>At Odd Position: {{$odd}}</div>
<span class="label"> {{note.label}}</span>
<span class="status" ng-bind="note.done"></span> <br/><br/>
</div>
Track by ID
デフォルトでは、ng-repeatは繰り返しの度に新しいDOM要素を作る。
しかし、パフォーマンス最適化のため、作成されるオブジェクトが同じであればDOMのキャッシュ又は再利用を行う。
(同一性の検証にはオブジェクトのハッシュ値を用いている)
オブジェクトの同一性に関係なく、同じDOM要素を再利用して欲しいこともある。
この場合には、AngularJSにトラッキングのための情報を渡すことができる。
<div ng-repeat="note in ctrl.notes2 track by note.id"> {{note.$$hashKey}}
<span class="label"> {{note.label}}</span>
<span class="author" ng-bind="note.done"></span>
</div>
track by にトラッキングのための変数を設定すると、この変数の値が同じであればDOM要素が再利用される。
$$ で始まる変数を使ってはいけない
$$ で始まる変数を、アプリケーションのコードで使ってはいけない。
AngularJSは $$ で始まる変数をAngularJS内のプライベート変数を示すものとして使用しており、これらの変数が将来に渡っても使える保証はない。
複数のHTML要素にまたがったng-repeat
テーブルを2行ごと(
ng-repeat-startとng-repeat-endディレクティブを使うことで、AngularJSに、ng-repeatの開始点と終了点を知らせることができる。
<tr ng-repeat-start="note in ctrl.notes">
<td>{{note.label}}</td>
</tr>
<tr ng-repeat-end>
<td>Done: {{note.done}}</td>
</tr>