目次
≪前の記事 1.初めの一歩
≫次の記事 3.サービス定義≪改訂≫
※【改訂】グローバル変数を使用しない記述法に改訂しました。
※最下部のサンプルコードをHTMLファイルとして保存し、ブラウザで表示、ブラウザの開発ツールなどでソースコードを確認しながら読んでください。
#フロントエンドMVC開発
初めの一歩では、AngularJSの強力なテンプレート&データバインディング機能を解説しましたが、これらはMVCフレームワークであるAngularJSの「V (View)」の部分だけです。
今回は「M (Model) 」と「C (Controller)」について解説します。
AngularJSでは、Controller はそのまま「コントローラー」、Model は「サービス」と呼びます。
#アプリケーションモジュール
AngularJSでは、アプリケーションを モジュール という単位で管理します。
AngularJSのコントローラ群は モジュール に属するオブジェクトとなります。
また、ビューで使用されるフィルタやディレクティブもモジュールに属します。
ビュー・テンプレート
|
+--モジュールA
| |
| +--コントローラーA
| +--コントローラーB
| +--ビュー・フィルタ、ディレクティブ
+--モジュールB
|
+--コントローラーC
+--コントローラーD
+--ビュー・フィルタ、ディレクティブ
このようにモジュールごとにコントローラとビューをグループに分けて管理できるようになっています。
また1つのテンプレートに複数のモジュールを紐付けて処理させることもできます。
ただし、よほどの複雑で大規模なアプリケーションでない限り、1つのアプリケーションで複数のモジュールを設ける必要はありません。
一般的には、1アプリケーション=1モジュールで十分対応できます。
##モジュールの定義
まずモジュールを定義します。
モジュールの定義は angular.module()
関数によってオブジェクトを生成することで行います。
var 変数 = angular.module(モジュール名, 依存モジュール[, 設定パラメータ])
1つ目の引数はモジュールの名前(文字列)です。
2つ目は依存する他のモジュールを指定するための配列です。通常は空配列を指定します。
3つ目はモジュールの諸設定を行うための関数オブジェクト(Module#config)を指定します。※省略可
なお、既存のモジュールオブジェクトを取得したい場合は、1つめの引数(モジュール名)のみを指定します。
var 変数 = angular.module(モジュール名)
以下は、「sampleModule」という名前のモジュールを生成する例です。
<script>
var module = angular.module("sampleModule", []);
</script>
###モジュールをテンプレートに紐付ける
モジュールを定義したら、そのモジュールをテンプレートに紐付けます。
テンプレートへの紐付けは、 ng-app ディレクティブ に属性値としてモジュール名を指定することで行います。
<... ng-app="<モジュール名>">
......
</...>
ng-app ディレクティブが指定された要素の範囲に対して、モジュールが紐付けられます。
<div ng-app="sampleModule">
〜 sampleModule によって管理される内容 〜
</div>
###即時関数
モジュール定義は、後述する関連する処理とともにひとまとまりとして処理させたいものになりますので、即時関数の中に入れておくとベターです。
※即時関数については 即時関数 と jQuery.readyイベント関数 を参照
(function()) {
var 変数 = angular.module(<モジュール名>);
〜 関連する処理 〜
return 変数
})();
即時関数内ではモジュールをローカル変数で扱い、最後に返値としてモジュールオブジェクトを返すようにします。また即時関数の返値を受け取るように変数を用意します。
なお、モジュールオブジェクトを以降の処理で参照できるよう、返値を受け取る変数はグローバル変数にします。
<script>
(function() {
var module = angular.module("sampleModule", []);
})();
</script>
#コントローラ
AngularJSでのコントローラは、モジュールオブジェクトによって定義されます。
前述の通り、1つのモジュール内に複数のコントローラの定義ができます。
なおMVCモデルにおけるコントローラは、あくまでユーザーのアクション(入力)に対する処理を記述するもので、この点はAngularJSでも同様です。
ビジネスロジックに相当する処理は後述するモデル(サービス)で作成するようにしましょう。
##コントローラの定義
コントローラの定義は モジュールオブジェクト の controller関数 で行います。
<モジュールオブジェクト>.controller(<コントローラ名>, function(<依存サービス>, ...) {
//
// コントローラの処理
//
});
1つ目の引数に コントローラ名(文字列)を指定します。
2つ目の引数には、コントローラの処理を記述する関数オブジェクトを渡します。この関数オブジェクトの引数には、コントローラ内で使用(依存)するサービス(モデル)を指定します。
なお、コントローラもモジュールと同様に、ひとまとまりで定義、処理させるため、ここでも即時関数を使います。
(function(<仮引数>) {
仮引数.controller(<コントローラ名>, function(<依存サービス>, ...) {
//
// コントローラの処理
//
});
})(<モジュールオブジェクト>);
処理の流れが少々わかりにくくなりますが、次のような流れです。
- 即時関数の 引数に モジュールオブジェクトを 渡す
- 即時関数の 仮引数が モジュールオブジェクトを 受け取る
- モジュールオブジェクトを受け取った仮引数(=モジュールオブジェクトの参照)で、controller関数を呼び出す
###依存サービス
コントローラ内で使用するサービスは、必ずコントローラ関数オブジェクトの引数に列挙されていなければなりません。この引数に指定されていないサービスは利用できません。
コントローラで必ず必要となるサービスが $scope です。詳細は後述しますが、$scope は コントローラとビューの間を取り持つ オブジェクトで、事実上必須のサービスといっていいでしょう。
以下はコントローラ定義のサンプルコードです。
(function(module) {
module.controller("sampleController", function($scope){
//
// コントローラの処理
//
});
})(angular.module("sampleModule")); // モジュールオブジェクトを取得して引数に渡す
###コントローラをテンプレートに紐付ける
定義したコントローラをテンプレートに紐付けることで、テンプレートの要素がコントローラで管理されるようになります。
テンプレートへの紐付けには ng-controller ディレクティブを使用します。
<... ng-controller="<コントローラ名>">
......
</...>
ある要素に ng-controller を指定すると、その要素以下がコントローラの管理下に置かれます。
1つのテンプレートに複数のコントローラを指定することも可能です。その場合は ng-controller を指定したそれぞれの要素が、それぞれのコントローラの管理下に置かれます。すなわち1つのページ内の部分部分で別のコントローラを利用することが可能です。
※ ng-controller は、 ng-app要素と同じか、それ以下の要素 に指定しなければなりません
<div ng-app="sampleModule">
<div ng-controller="sampleController">
〜 sampleControllerによって管理される内容 〜
</div>
</div>
#サービス
AngularJSにおいて、MVCの「M(Model)」に相当するのが サービス です。
様々な 標準サービス が利用できるほか、ビジネスロジックを記述するモデルとして 独自のサービス を定義することも可能です。
##よく使われる標準サービス
###$scope
前述したとおり、コントローラとビューの間を取り持つサービスオブジェクトです。
$scopeが持つプロパティや関数オブジェクトは全てビュー内に展開されます。
したがって、変数や関数オブジェクトを $scope にセットすることで、テンプレート内でそれらを利用できるようになります。
(function(<仮引数>) {
仮引数.controller(<コントローラ名>, function($scope, ...) {
$scope.プロパティ名 = 値;
$scope.関数名 = function(仮引数, ...) {
......
}
});
})(<モジュールオブジェクト>);
このようにコントローラ処理内で、$scopeのプロパティとして値や関数オブジェクトを渡します。
テンプレートでは、
{{プロパティ名 または 関数名()}}
{{プロパティ名 または 関数名() | フィルタ}}
<... ng-bind="プロパティ名 または 関数名()"></...>
<... ng-bind="プロパティ名 または 関数名() | フィルタ"></...>
このように $scope にセットしたプロパティや関数名をしていするだけで、値 や 関数の返り値 がバインドされます。
(function(module) {
module.controller("sampleController", function($scope){
$scope.title = "AngularJSサンプル02";
$scope.message = function() {
return "このメッセージが表示されましたか?";
}
});
})(angular.module("sampleModule"));
<div ng-app="sampleModule">
<div ng-controller="sampleController">
<h1>{{title}}</h1>
<p>{{message()}}</p>
</div>
</div>
###$sce
AngularJSのセキュリティ機能「Strict Contextual Escaping」を提供するサービスです。
このサービスは、最終的にテンプレートにバインドされる値を安全なもの(サニタイズ(殺菌)済みのもの)にする処理を自動的に行います。
デフォルトで有効になっており、依存サービスとして指定しなくても機能しています。
ただし、一時的にSCEを迂回するときなど、コントローラ内で SCE を制御しなければならない場合は、依存サービスに $sce を指定する必要があります。
####$sceを迂回する例
例えば改行を <br> タグに変換して出力する場合、<br>タグがSCEによって危険な出力としてサニタイズされてしまうため、これを迂回する必要があります。
安全であると確信できるHTMLタグを、SCEを迂回させてバインドするには、$sce.trustAsHtml()
関数を使用します。
<script>
(function(module) {
module.controller("sampleController", function($scope, $sce){
$scope.messageBr = $sce.trustAsHtml("このメッセージは<br>改行されて表示されていますか?");
});
})(angular.module("sampleModule"));
</script>
<div ng-app="sampleModule">
<div ng-controller="sampleController">
<p ng-bind-html="messageBr"></p>
</div>
</div>
###$timeout
$timeout サービスは、JavaScriptの setTimeout() 関数と同等の働きをするサービスです。
AngularJSのデータバインド処理は、その性格上、 コントローラとビューでのデータバインドのタイミングに微妙なタイムラグが生じます。そのため、コントローラでバインドした値がビューに反映されない(画面上で変化しない)、あるいはビューで入力した値をコントローラで受け取れない場合があります。
$timeout サービスを利用することでこのような事象を回避することができます。
コントローラ内で $scope にデータをバインドする、あるいは$scopeの値を処理する場合には、 必ず $timeout を利用する ことが望ましいです。
$timeout サービスを利用するには、コントローラ定義で依存サービスに $timeout を追加する必要があります。
$timeout(function(){
〜 タイムラグの影響を防ぎたい処理 〜
});
(function(module) {
module.controller("sampleController", function($scope, $sce, $timeout){
$timeout(function(){
$scope.title = "AngularJSサンプル02";
$scope.message = function() {
return "このメッセージが表示されましたか?";
}
$scope.messageBr = $sce.trustAsHtml("このメッセージは<br>改行されて表示されていますか?");
});
});
})(angular.module("sampleModule"));
ただし、$timeout の利用は処理コストが少なくないので、利用は可能な限り最小限にとどめるようにしたほうがよい、という意見もあります。
したがって実際に検証した上で、タイムラグの影響がある箇所を $timeout で処理するのが望ましいでしょう。
####ng-cloak
AngularJSで処理されるHTMLテンプレートを表示する際、一瞬だけテンプレートタグ {{ ... }}
がそのまま表示されてしまう場合があります。
これは「ちらつき」と呼ばれ、ブラウザでのHTMLの出力と、AngularJSビューによるデータバインドのタイムラグで生じてしまう問題です。
ng-cloak ディレクティブは、この「ちらつき」を防ぐためのディレクティブです。
ng-controller 以下の要素に指定します。
ng-controller が指定されている要素にしていすれば、ng-controller 以下全体で有効になります。
あるいは、データをバインドしている箇所(要素)に個別に指定することも可能です。
処理コストは後者のほうが低いとされています。
<div ng-app="sampleModule">
<div ng-controller="sampleController" ng-cloak>
<h1>{{title}}</h1>
<p>{{message()}}</p>
<p ng-bind-html="messageBr"></p>
</div>
</div>
#全体のサンプルコード
<!DOCTYPE html>
<meta charset="UTF-8">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<script>
/**
* アプリケーション モジュール オブジェクトの定義
*/
SampleModule = (function() {
var module = angular.module("sampleModule", []);
})();
/**
* コントローラの定義
*/
(function(module) {
/**
* SampleController
*/
module.controller("sampleController", function($scope, $sce, $timeout){
$timeout(function(){
// $scopeに値、関数をバインド
$scope.title = "AngularJSサンプル02";
$scope.message = function() {
return "このメッセージが表示されましたか?";
}
$scope.messageBr = $sce.trustAsHtml("このメッセージは<br>改行されて表示されていますか?");
});
});
})(angular.module("sampleModule"));
</script>
<!-- sampleModule テンプレート -->
<div ng-app="sampleModule">
<!-- sampleController テンプレート -->
<div ng-controller="sampleController" ng-cloak>
<h1>{{title}}</h1>
<p>{{message()}}</p>
<p ng-bind-html="messageBr"></p>
</div>
</div>
目次
≪前の記事 1.初めの一歩
≫次の記事 3.サービス定義≪改訂≫