AngularJSに触れてみる その1
AngularJSはGoogle社が開発しているJavaScriptのMVCフレームワークです。Webの技術を使うMonacaでももちろん使うことができます。また、jQueryなどの他のライブラリと併用することもできます。MVCとはModel(モデル)、View(ビュー)、Controller(コントローラー)の略称でありそれぞれのコンポーネントにアプリケーション中の役割を分割する思想、手法です。
Model:アプリケーション内で使うデータ構造。
View:マークアップなどアプリケーションのユーザーの実際に目にするもの。
Controller:アプリ内で使うデータを操作するコンポーネントであり、ModelとViewを操作するもの。
AngularJSのMVCに関して本家ドキュメントへのリンクを貼っておきますので、詳しくはこちらを参照してください。
まずは、AngularJSを使った例を見てみましょう。
Example 1: Hello World
[index.html]
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
<script src="app.js"></script>
</head>
<body ng-app>
<div ng-controller="firstCtrl">
<p>{{hello}}</p>
</div>
<div ng-controller="secondCtrl">
<p>{{hello}}</p>
</div>
<div ng-controller="thirdCtrl">
<p>{{hello}}</p>
</div>
</body>
</html>
[app.js]
function firstCtrl($scope){
$scope.hello = "Hello Angular!";
};
function secondCtrl($scope){
$scope.hello = "Hello Monaca!";
};
function thirdCtrl($scope){
$scope.hello = "Hello World!";
};
下の画像は上記のコードをMonacaのライブプレビューで動かしてみた画像です。ご興味のある方はMonacaやjsFiddleやPlunkerなどで動かしてみてください。
{{hello}}というAngularのExpressionsがそれぞれのコントローラー(firstCtrlやsecondCtrl)中のデータ($scope.hello)に対応してビュー( 下図)として表示されていることが見えると思います。
以下コードについて簡単に見ていきたいと思います。
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
このコードはAngular.jsをロードします。
<body ng-app>
ng-appはAngularJSを適用するとアプリケーションに宣言します。これを外すとbodyにAngularJSが適用されないため、{{hello}}がそのまま表示されます。
<div ng-controller="firstCtrl">
<p>{{hello}}</p>
</div>
アプリケーション内のdivタグ内にapp.jsで定義されているfirstCtrlコントローラーを適用することを宣言します。コントローラー(Controller)はそれぞれに固有のスコープ($scope)を保持しています。
{{ ... }}はAgularJSのExpressionsです。ここでは{{hello}}と記述されています。
ここで{{hello}}が参照している値は$scope.helloです。app.jsを見ると$scope.helloの値は各コントローラーごとに異なっていますので、それぞれ異なる値が上の画像では表示されています。例えば、fistCtrl中の$scope.helloの値は"Hello Angular!"となっていますので、firstCtrlが適用されているdivタグ内の{{hello}}には "Hello Angular!"と表示されます。{{ ... }}はコントローラーの$scopeと結びついています 。
$scope.hello = "Hello Angular!";
スコープ($scope)はアプリケーションのモデル (Model) を保持します。もちろん文字列や数値など以外に関数やオブジェクトも入れておくことができます。コントローラー (Controller) はその関数固有の$scopeを持っています。
function firstCtrl($scope){$scope.hello = "Hello Angular!";};
firstCtrlというコントローラーの定義です。モデル (Model) として内部に$scope.hello = "Hello Angular!"を包含しています。
$scopeには関数も入れることができます。
下記の例では $scope.nameに保持された"Monaca!!!"という文字列を
grtMonaca()という関数を使って取得しています。getMonaca()関数自体は
{{ ... }}の中に記述しています。
[index.html]
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
<script src="app.js"></script>
</head>
<body ng-app>
<!--MonaCtrlコントローラーを適用-->
<div ng-controller="MonaCtrl">
<p>{{getMonaca()}}</p>
</div>
</body>
</html>
[app.js]
//MonaCtrlコントローラーを定義
function MonaCtrl($scope){
$scope.name = "Monaca!!!";
$scope.getMonaca = function(){
return $scope.name;
}
}
Example 2:AngularJSを使ったデータバインディング
下記がコードとなります。
[index.html]
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
</head>
<body ng-app>
<div>
<input type="text" ng-model="name">
<p>こんにちは {{name}} さん!</p>
</div>
</body>
</html>
ng-model="name"という形でinputにModel (モデル) を指定しています。こうすることで$scope.nameというModel(モデル)が 作成されます。そして、{{name}}は$scope.nameを参照します。こうすることでng-model="name"と指定された入力フォームと{{name}}の箇所をバインディングすることができます。$scopeがModelとViewとを繋ぐ糊の役割を果たしています。
Example 3:ちょっと項目数を増やした例
入力フォームの例です。黒あん最中は50個まで、白あん最中は20個まで、粒あん最中は15個まで購入できます。5000円以上買うと、送料500円が無料になります。
下記がコードになります。 ※主要な部分以外は省略してあります。
[index.html]
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
<script src="app.js"></script>
</head>
<body ng-app>
<div ng-controller="MonaCtrl" >
<ul>
<li ng-repeat="item in items" >{{item.name}}:{{item.price}}円</li><br/>
<li>{{items.item1.name}}購入:<input type="number" ng-model="item1" min="0" />個</input></li>
<li>{{items.item2.name}}購入:<input type="number" ng-model="item2" min="0" />個</input></li>
<li>{{items.item3.name}}購入:<input type="number" ng-model="item3" min="0" />個</input></li>
<li>お買い上げ数:{{ getAmount() | number }}個</li>
<li>購入代金:{{ getPayment() | number }}円 </li>
<li>{{shippingMessage}}:{{shipping}}円</li>
<li>総代金:{{getTotalcost() | number}}:円</li>
</ul>
</div>
</body>
</html>
[app.js]
function MonaCtrl($scope){
//各Modelの値を初期化する
$scope.item1 = 0;
$scope.item2 = 0;
$scope.item3 = 0;
$scope.shippingMessage = "";
$scope.shipping = 0;
$scope.items = {
item1 : {
name : "黒あん最中",
price : 100
},
item2 : {
name : "白あん最中",
price : 120
},
item3 : {
name : "粒あん最中",
price : 90
}
};
$scope.getAmount = function(){
return $scope.item1 + $scope.item2 + $scope.item3;
}
$scope.getPayment = function(){
return $scope.item1 * $scope.items.item1.price + $scope.item2 * $scope.items.item2.price + $scope.item3 * $scope.items.item3.price;
}
$scope.getTotalcost = function(){
return $scope.getPayment() + $scope.shipping;
}
//$watch関数でgetPayment()の値を監視する。監視結果に応じて$scope.shippingMessage、$scope.shipping、すなわち送料メッセージと送料の値を変更。
$scope.$watch("getPayment()", function(newValue, oldValue){
if($scope.getPayment() < 5000){
$scope.shippingMessage = "送料";
$scope.shipping = 500;
}else if($scope.getPayment() >= 5000){
$scope.shippingMessage = "送料無料!";
$scope.shipping = 0;
}
});
}
app.jsからコードを見ていきます。
[app.js]抜粋
function MonaCtrl($scope){
$scope.item1 = 0;
$scope.item2 = 0;
$scope.item3 = 0;
$scope.shippingMessage = "";
$scope.shipping = 0;
$scope.items = {
item1 : {
name : "黒あん最中",
price : 100
},
item2 : {
name : "白あん最中",
price : 120
},
item3 : {
name : "粒あん最中",
price : 90
}
};
...
}
MonaCtrlというコントローラーを定義していて、値の初期化のためにここで、$scope.item1などに値を代入しています。$scopeの中にはもちろん、オブジェクトも入れることができます。index.htmlに移りましょう。
[index.html]抜粋
<body ng-app>
<div ng-controller="MonaCtrl" >
<ul>
<li ng-repeat="item in items" >{{item.name}}:{{item.price}}円</li><br/>
...
</ul>
...
</div>
</body>
ng-appでAngularJSの適用を行います。div ng-controller="MonaCtrl"でdivタグにMonaCtrlコントローラーの適用を行います。
<li ng-repeat="item in items" >{{item.name}}:{{item.price}}円</li><br/>
ng-repeatディレクティブは配列やオブジェクトをループさせることができるディレクティブです。ここではitemsオブジェクトの中身をループさせて{{item.name}}、{{item.price}}という形で展開を行っています。
<li>{{items.item1.name}}購入:<input type="number" ng-model="item1" min="0" />個</li>
<li>{{items.item2.name}}購入:<input type="number" ng-model="item2" min="0" />個</li>
<li>{{items.item3.name}}購入:<input type="number" ng-model="item3" min="0" />個</li>
input [number]に指定されているng-model="item1"、ng-model="item2"などは商品の購入量です。$scope.item1、$scope.item2等のデータと結びついています。app.jsで値を0で初期化しているためにフォームの初期値には0が入ります。min=0と指定することで最小値を0にして、負の値が入力された際にエラーになるようにしています。input [number]にはmin以外にも色々指定できるパラメーターがあります。
...
<li>お買い上げ数:{{ getAmount() | number }}個</li>
<li>購入代金:{{ getPayment() | number }}円 </li>
<li>{{shippingMessage}}:{{shipping}}円</li>
<li>総代金:{{getTotalcost() | number}}:円</li>
...
{{getAmount()}}、{{getPayment()}}でapp.jsに定義されている関数を{{ ... }} (Expressions) 中で呼び出しています。関数を呼び出した結果に対して「getPayment() | number」という形でフィルターをかけています。numberフィルターをかけることによって、getPayment()で出力されたものが数字以外であれば、おかしな出力をそのまま返さず、空文字列を返すようになります。
app.jsに移りましょう。
[app.js]抜粋
$scope.getAmount = function(){
return $scope.item1 + $scope.item2 + $scope.item3;
}
$scope.getPayment = function(){
return $scope.item1 * $scope.items.item1.price + $scope.item2 * $scope.items.item2.price + $scope.item3 * $scope.items.item3.price;
}
$scope.getTotalcost = function(){
return $scope.getPayment() + $scope.shipping;
}
ちょっと書き方が見にくいかもしれませんが、それぞれ商品の商品の購入量や商品の合計代金を計算する関数を定義しています。またgetPayment()は合計代金に送料を足した総代金を計算する関数です。ユーザーが入力フォームにそれぞれ入力した合計がその都度、計算されて、index.htmlの{{getAmount()}}などのExpressions中に表示されます。
$scope.$watch("getPayment()", function(newValue, oldValue){
if($scope.getPayment() < 5000){
$scope.shippingMessage = "送料";
$scope.shipping = 500;
}else if($scope.getPayment() >= 5000){
$scope.shippingMessage = "送料無料!";
$scope.shipping = 0;
}
});
$watch関数は AngularJSのScopeオブジェクトが提供する関数です。値の監視を行うことができます。監視したいデータを第1引数にとります。ここではgetPayment()の値を監視しています。そして、第2引数にリスナーを関数として指定します。第2引数中の関数の引数である、newValueとoldValueですが、監視対象のデータに変更があるたび、$watchがそれを検出して、新しい値がnewValueに、元の値がoldValue中に格納される挙動になります。
if($scope.getPayment() < 5000){
$scope.shippingMessage = "送料";
$scope.shipping = 500;
}else if($scope.getPayment() >= 5000){
$scope.shippingMessage = "送料無料!";
$scope.shipping = 0;
}
ここではgetPayment()に変更があるたびに上記の条件分岐ロジックが走ります。$Payment()、すなわち商品代金が5000よりも少なければ、送料は500円。5000円以上であれば、送料は0円、無料になります。ここで変更されている$scope.shippingはindex.htmlの{{shipping}}に結びついています。
ちょっと長くなりましたが、以上になります。すぐにMonacaで動かせるコードをダウンロードできるようにしておきますので、ご興味のある方はダウンロードして動かしてみてください。
私自身勉強不足なので何か間違いなどございましたら、ご指摘していただけると嬉しいです。