Asial Blog

Recruit! Asialで一緒に働きませんか?

AngularJSに触れてみる その1

カテゴリ :
フロントエンド(HTML5)
タグ :
Monaca
JavaScript
HTML5
Tech
Webサイト
AngularJSはGoogle社が開発しているJavaScriptのMVCフレームワークです。Webの技術を使うMonacaでももちろん使うことができます。また、jQueryなどの他のライブラリと併用することもできます。MVCとはModel(モデル)、View(ビュー)、Controller(コントローラー)の略称でありそれぞれのコンポーネントにアプリケーション中の役割を分割する思想、手法です。

Model:アプリケーション内で使うデータ構造。
View:マークアップなどアプリケーションのユーザーの実際に目にするもの。
Controller:アプリ内で使うデータを操作するコンポーネントであり、ModelとViewを操作するもの。


AngularJSのMVCに関して本家ドキュメントへのリンクを貼っておきますので、詳しくはこちらを参照してください。

Model
View
Controller


まずは、AngularJSを使った例を見てみましょう。

Example 1: Hello World



[index.html]

  1. <!DOCTYPE HTML>
  2. <html>
  3. <head>
  4.     <meta charset="utf-8">
  5.     <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
  6.     <script src="app.js"></script>
  7. </head>
  8. <body ng-app>
  9.  
  10.   <div ng-controller="firstCtrl">
  11.     <p>{{hello}}</p>
  12.   </div>
  13.   
  14.   <div ng-controller="secondCtrl">
  15.     <p>{{hello}}</p>
  16.   </div>
  17.  
  18.   <div ng-controller="thirdCtrl">
  19.     <p>{{hello}}</p>
  20.   </div>
  21.  
  22. </body>
  23. </html>

[app.js]

  1. function firstCtrl($scope){
  2.   $scope.hello = "Hello Angular!";
  3. };
  4.  
  5. function secondCtrl($scope){
  6.   $scope.hello = "Hello Monaca!";
  7. };
  8.  
  9. function thirdCtrl($scope){
  10.   $scope.hello = "Hello World!";
  11. };

下の画像は上記のコードをMonacaのライブプレビューで動かしてみた画像です。ご興味のある方はMonacajsFiddlePlunkerなどで動かしてみてください。



{{hello}}というAngularのExpressionsがそれぞれのコントローラー(firstCtrlやsecondCtrl)中のデータ($scope.hello)に対応してビュー(下図)として表示されていることが見えると思います。

以下コードについて簡単に見ていきたいと思います。




  1. <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>

このコードはAngular.jsをロードします。

  1. <body ng-app>

ng-appはAngularJSを適用するとアプリケーションに宣言します。これを外すとbodyにAngularJSが適用されないため、{{hello}}がそのまま表示されます。



  1. <div ng-controller="firstCtrl">
  2. <p>{{hello}}</p>
  3. </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と結びついています。


  1. $scope.hello = "Hello Angular!";

スコープ($scope)はアプリケーションのモデル (Model) を保持します。もちろん文字列や数値など以外に関数やオブジェクトも入れておくことができます。コントローラー (Controller) はその関数固有の$scopeを持っています。

  1. function firstCtrl($scope){$scope.hello = "Hello Angular!";};

firstCtrlというコントローラーの定義です。モデル (Model) として内部に$scope.hello = "Hello Angular!"を包含しています。


$scopeには関数も入れることができます。

下記の例では $scope.nameに保持された"Monaca!!!"という文字列を
grtMonaca()という関数を使って取得しています。getMonaca()関数自体は
{{ ... }}の中に記述しています。

[index.html]

  1. <!DOCTYPE HTML>
  2. <html>
  3. <head>
  4.     <meta charset="utf-8">
  5.     <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
  6.     <script src="app.js"></script>
  7. </head>
  8. <body ng-app>
  9.  
  10.   <!--MonaCtrlコントローラーを適用-->
  11.   <div ng-controller="MonaCtrl">
  12.     <p>{{getMonaca()}}</p>
  13.   </div>
  14. </body>
  15. </html>

[app.js]

  1. //MonaCtrlコントローラーを定義
  2. function MonaCtrl($scope){
  3.   $scope.name = "Monaca!!!";
  4.   
  5.   $scope.getMonaca = function(){
  6.     return $scope.name;
  7.   }
  8. }


Example 2:AngularJSを使ったデータバインディング




下記がコードとなります。

[index.html]

  1. <!DOCTYPE HTML>
  2. <html>
  3. <head>
  4.     <meta charset="utf-8">
  5.     <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
  6. </head>
  7. <body ng-app>
  8.   <div>
  9.     <input type="text" ng-model="name">
  10.     <p>こんにちは {{name}} さん!</p>
  11.   </div>
  12. </body>
  13. </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]
  1. <!DOCTYPE HTML>
  2. <html>
  3. <head>
  4.     <meta charset="utf-8">
  5.     <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
  6.     <script src="app.js"></script>
  7. </head>
  8. <body ng-app>
  9.   <div ng-controller="MonaCtrl" >
  10.     <ul>
  11.       <li ng-repeat="item in items" >{{item.name}}:{{item.price}}円</li><br/>
  12.       <li>{{items.item1.name}}購入:<input type="number" ng-model="item1" min="0" /></input></li>
  13.       <li>{{items.item2.name}}購入:<input type="number" ng-model="item2" min="0" /></input></li>
  14.       <li>{{items.item3.name}}購入:<input type="number" ng-model="item3" min="0" /></input></li>
  15.       <li>お買い上げ数:{{ getAmount() | number }}個</li> 
  16.       <li>購入代金:{{ getPayment() | number }}円 </li>
  17.       <li>{{shippingMessage}}:{{shipping}}円</li>
  18.       <li>総代金:{{getTotalcost() | number}}:円</li>
  19.     </ul>
  20.   </div>
  21. </body>
  22. </html>


[app.js]

  1. function MonaCtrl($scope){
  2.   
  3.   //各Modelの値を初期化する
  4.   $scope.item1 = 0;
  5.   $scope.item2 = 0;
  6.   $scope.item3 = 0;
  7.   $scope.shippingMessage = "";
  8.   $scope.shipping = 0;
  9.   
  10.   $scope.items = {
  11.     item1 : {
  12.       name : "黒あん最中",
  13.       price : 100
  14.     },
  15.     item2 : {
  16.       name : "白あん最中",
  17.       price : 120
  18.     },
  19.     item3 : {
  20.       name : "粒あん最中",
  21.       price : 90
  22.     }
  23.   };
  24.   
  25.   $scope.getAmount = function(){
  26.     return  $scope.item1 + $scope.item2 + $scope.item3;
  27.   }
  28.     
  29.   $scope.getPayment = function(){    
  30.     return  $scope.item1 * $scope.items.item1.price + $scope.item2 * $scope.items.item2.price + $scope.item3 * $scope.items.item3.price; 
  31.   } 
  32.  
  33.   $scope.getTotalcost = function(){    
  34.     return  $scope.getPayment() + $scope.shipping;
  35.   } 
  36.   
  37.   //$watch関数でgetPayment()の値を監視する。監視結果に応じて$scope.shippingMessage、$scope.shipping、すなわち送料メッセージと送料の値を変更。
  38.   $scope.$watch("getPayment()", function(newValue, oldValue){
  39.     if($scope.getPayment() < 5000){
  40.       $scope.shippingMessage = "送料";   
  41.       $scope.shipping = 500; 
  42.     }else if($scope.getPayment() >= 5000){
  43.       $scope.shippingMessage = "送料無料!";     
  44.       $scope.shipping = 0;
  45.     }
  46.   });
  47. }

app.jsからコードを見ていきます。

[app.js]抜粋


  1. function MonaCtrl($scope){
  2.  
  3.   $scope.item1 = 0;
  4.   $scope.item2 = 0;
  5.   $scope.item3 = 0;
  6.   $scope.shippingMessage = "";
  7.   $scope.shipping = 0;
  8.   
  9.   $scope.items = {
  10.     item1 : {
  11.       name : "黒あん最中",
  12.       price : 100
  13.     },
  14.     item2 : {
  15.       name : "白あん最中",
  16.       price : 120
  17.     },
  18.     item3 : {
  19.       name : "粒あん最中",
  20.       price : 90
  21.     }
  22.   };
  23.  
  24.  ...
  25.  
  26. }

MonaCtrlというコントローラーを定義していて、値の初期化のためにここで、$scope.item1などに値を代入しています。$scopeの中にはもちろん、オブジェクトも入れることができます。index.htmlに移りましょう。

[index.html]抜粋

  1. <body ng-app>
  2.   <div ng-controller="MonaCtrl" >
  3.     <ul>
  4.       <li ng-repeat="item in items" >{{item.name}}:{{item.price}}円</li><br/>
  5.  
  6.     ...
  7.  
  8.     </ul>
  9.  
  10.     ...
  11.  
  12.   </div>
  13. </body>

ng-appでAngularJSの適用を行います。div ng-controller="MonaCtrl"でdivタグにMonaCtrlコントローラーの適用を行います。

  1. <li ng-repeat="item in items" >{{item.name}}:{{item.price}}円</li><br/>

ng-repeatディレクティブは配列やオブジェクトをループさせることができるディレクティブです。ここではitemsオブジェクトの中身をループさせて{{item.name}}、{{item.price}}という形で展開を行っています。


  1. <li>{{items.item1.name}}購入:<input type="number" ng-model="item1" min="0" /></li>
  2. <li>{{items.item2.name}}購入:<input type="number" ng-model="item2" min="0" /></li>
  3. <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以外にも色々指定できるパラメーターがあります。

  1. ...
  2.       <li>お買い上げ数:{{ getAmount() | number }}個</li> 
  3.       <li>購入代金:{{ getPayment() | number }}円 </li>
  4.       <li>{{shippingMessage}}:{{shipping}}円</li>
  5.       <li>総代金:{{getTotalcost() | number}}:円</li>
  6.     ...

{{getAmount()}}、{{getPayment()}}でapp.jsに定義されている関数を{{ ... }} (Expressions) 中で呼び出しています。関数を呼び出した結果に対して「getPayment() | number」という形でフィルターをかけています。numberフィルターをかけることによって、getPayment()で出力されたものが数字以外であれば、おかしな出力をそのまま返さず、空文字列を返すようになります。


app.jsに移りましょう。

[app.js]抜粋

  1. $scope.getAmount = function(){
  2.     return  $scope.item1 + $scope.item2 + $scope.item3;
  3.   }
  4.     
  5.   $scope.getPayment = function(){    
  6.     return  $scope.item1 * $scope.items.item1.price + $scope.item2 * $scope.items.item2.price + $scope.item3 * $scope.items.item3.price; 
  7.   } 
  8.  
  9.   $scope.getTotalcost = function(){    
  10.     return  $scope.getPayment() + $scope.shipping;
  11.   }

ちょっと書き方が見にくいかもしれませんが、それぞれ商品の商品の購入量や商品の合計代金を計算する関数を定義しています。またgetPayment()は合計代金に送料を足した総代金を計算する関数です。ユーザーが入力フォームにそれぞれ入力した合計がその都度、計算されて、index.htmlの{{getAmount()}}などのExpressions中に表示されます。

  1. $scope.$watch("getPayment()", function(newValue, oldValue){
  2.     if($scope.getPayment() < 5000){
  3.       $scope.shippingMessage = "送料";   
  4.       $scope.shipping = 500; 
  5.     }else if($scope.getPayment() >= 5000){
  6.       $scope.shippingMessage = "送料無料!";     
  7.       $scope.shipping = 0;
  8.     }
  9.   });

$watch関数は AngularJSのScopeオブジェクトが提供する関数です。値の監視を行うことができます。監視したいデータを第1引数にとります。ここではgetPayment()の値を監視しています。そして、第2引数にリスナーを関数として指定します。第2引数中の関数の引数である、newValueとoldValueですが、監視対象のデータに変更があるたび、$watchがそれを検出して、新しい値がnewValueに、元の値がoldValue中に格納される挙動になります。

  1. if($scope.getPayment() < 5000){
  2.       $scope.shippingMessage = "送料";   
  3.       $scope.shipping = 500; 
  4. }else if($scope.getPayment() >= 5000){
  5.       $scope.shippingMessage = "送料無料!";     
  6.       $scope.shipping = 0;
  7. }

ここではgetPayment()に変更があるたびに上記の条件分岐ロジックが走ります。$Payment()、すなわち商品代金が5000よりも少なければ、送料は500円。5000円以上であれば、送料は0円、無料になります。ここで変更されている$scope.shippingはindex.htmlの{{shipping}}に結びついています。

ちょっと長くなりましたが、以上になります。すぐにMonacaで動かせるコードをダウンロードできるようにしておきますので、ご興味のある方はダウンロードして動かしてみてください。

ダウンロード



私自身勉強不足なので何か間違いなどございましたら、ご指摘していただけると嬉しいです。

コメント

  • YOU

    これからmonaca使ってプログラミングの勉強をしようと思っていますが、AngularJSを使うと何かいいことがあるのでしょうか?
    勉強しなければいけないことが多いので、あまりメリットがないのであれば、対象から外そうと思っています。
    お忙しいところ恐縮ですが、ご教示お願いします。

  • Monaca

    YOU様

    コメントいただき、ありがとうございます。

    もし、JavaScript自体が初めてでしたら、いきなりAngularJSをおすすめできるかどうか解りません。
    恐らく、まず生のJavaScriptやjQueryなどのメジャーなフレームワークを使って書いてみる方が良いかもしれません。AngularJSのドキュメント等もJavaScriptの基礎の理解を前提として書かれています。

    詰まった時にはどうすれば良いのかAngularJSはまだ日本語の文献や情報が少なく、英語の情報源に頼らざるを得ない状況です。jQueryでしたら日本語の文献も数多あります。

    メリットとしては

    まず第1に書くコード量が少なくて済みます。

    Example2のコードですが、jQueryなど他のフレームワークで書こうとすると、下記のようにコードがちょっと長くなります。

    <html>
    <head>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
    <script>
    $(document).ready(function(){
    $("input").keypress(function(){
    $("#name").text($(this).val());
    });
    })
    </script>
    </head>
    <body>
    <input type="text"></input>
    <p id="name"></p>
    </body>
    </html>

    jQueryではなく生のJavaScriptでクロスブラウザを考えながらこれを書こうとするとコード量はもっと長くなります。ちょっと正直書きたくありません。

    他にも同じJavaScriptで書いたコードがGoogle Chromeでは動くのに、Internet ExplorerやFireFoxでは動かないといったクロスブラウザの問題にもある対応してくれるなど沢山色々なメリットがあります。

    こういったメリットは言葉や例のみで伝えても非常に伝えるのが難しい部分があるので、実際に生のJavaScriptやjQueryでコードを沢山書いてみて、それからAngularJSに触ってみると、便利な機能が沢山用意されていることを実感できると思われます。

    ブログを読んでいただき、ありがとうございます。
    頑張ってください。

  • YOU

    Monaca様

    ご丁寧に詳細ご説明頂きどうもありがとうございます。

    20年近く前に大学卒業後、社会人になってはじめてCOBOLを使い始め、ここ10年以上プログラミングしておらず、最近、HTMLからjavascript、Jquery、jquerymobileをドットインストールで勉強したばかりですので、AngularJSは一旦おいておくことにします。

    今、まさにMonacaのサンプルアプリで実際のスマホアプリの勉強を始めたばかりで、こちらのブログでは、いつも勉強させて頂いております。
    今後とも有益な情報発信頂けると幸甚です。(Monacaでsqliteを使ったアプリやlocalstrageとsqliteの使い分けの基準等があるとうれしいです。)

  • Monaca

    YOU様

    御返信いただき、ありがとうございます。
    大塔です。

    >(Monacaでsqliteを使ったアプリやlocalstrageとsqliteの使い分けの基準等があるとうれしいです。)

    ありがとうございます。
    引き続き、Monacaドキュメントではサンプルアプリや開発Tipsを拡充して参ります。
    また、ブログではPHP、HTML5、JavaScript関連の情報発信を行っていきます。

    今後とも、アシアルをよろしくお願いいたします。

  • 通りすがり

    通りすがりで失礼します。
    $scope.itemsの配列ですが、javascriptでオブジェクトをハッシュとして使った場合、
    PHPのように順序を保存しないので、
    ng-repeatの部分の表示は、処理系依存になるのではないかと思います。

  • Monaca

    通りすがり様

    コメントありがとうございます。プロパティの取り出される順序がJavaScriptの処理系に依存するというのは仰る通りだと思います。順序を保つことを保証したい場合には配列を使った方が良いと思います。

    実際にngRepeatで宣言した順序で取り出されなかったという書き込みも本家ドキュメントの掲示板にもあることから、順序を保持したい場合には別の手段を使った方が良いと思います。

    http://docs.angularjs.org/api/ng.directive:ngRepeat

    ちょうど先日、通りすがり様にご指摘いただいたことと同じことが気になってngRepeatのドキュメントを見ていたのですが、このことに関連する興味深いやり取りが掲示板上でなされていましたので、意訳した上で転載します。

    Why does ngRepeat not maintain the order of the items while using (key, value) ?

    Amyth Singh氏
    ngRepeatは何でkey、valueを使った時に項目の順序を保持しないんだろう?


    Because an associative array (which is what an array that has key/value pairs is called) doesn't have an order. If you want order, use a standard array.

    Christian Dannie Storgaard氏
    連想配列 (keyと値のペアでなる配列) は順序を保持しないので、もし順序を保持したければ通常の配列を使うと良いですよ。

    That's not true. The ECMAscript standard now stipulates the behavior that every JS engine already implemented: the declaration order of object properties must be preserved, and iteration must happen in the same order. (JS has no concept of an “associative array”, just objects.) It is hardly unreasonable to have expected Angular to have followed the standard behavior.

    Eric Naeseth氏:
    それは間違いです。 ECMAscriptは全てのJSエンジンが実装した振る舞いを明記していますよ。オブジェクトの宣言の順序は保存されるはずです。そして反復処理はそれと同じ順番で必ず実行されます。そして、JavaScriptにはそもそも連想配列なんて概念は無くてオブジェクトですよ。
    そして、そのECMAScriptが定めた基準となる振る舞いにAngularJSは従っていると考えるのが理に適っています。

    While most browsers do indeed following this behaviour, I haven't been able to find it in the ECMAScript 5.1 edition standard. What I could find, was the spec for for loops [12.6.4]: "The mechanics and order of enumerating the properties (step 6.a in the first algorithm, step 7.a in the second) is not specified." - I couldn't find anything more specific. If it's in the standard somewhere, I still think Angulars behaviour makes more sense. As objects often have more initialisation logic around them than arrays, I think it's fair to assume that the order often might not be what the user actually wants/expected, so forcing a specific sorting ensure the proper behaviour - especially when we also have to deal with older browsers.

    Christian Dannie Storgaard氏
    ほとんどのWebブラウザは宣言した順序にオブジェクトを取り出すという振る舞いに実際には従っているけれども、その明記された基準ってのはECMAScript 5.1の中にはなかったですよ。ループ処理の仕様はありましたが...。プロパティを列挙する時のメカニズムおよび順序の仕様は明記されてなかった。他にそれに関する仕様らしき記述も見つけることができませんでした。でも、たとえ順序の保持が基準としてどこかに明記されていたとしても、AngularJSの振る舞い方はやはり理に適っていると思います。なぜならオブジェクトは順序を保持することよりも値の初期化のために用いられることが配列よりも断然に多いので、ユーザーが期待し、欲するようにはオブジェクトでは順序は保持されないとするのが正しいと思います。これは特に古いブラウザに対応させなければならない場合に関して言えることなのですが、順序の保持を強制することが期待したような振る舞いをJavaScriptにさせることの保証になると思います。

  • ししまる

    本サイトを参考に、初めてAngular.jsを触っている初心者です。本サイトのお陰で、基本部分を把握でき、大変参考になりました。
    Example3において、item1,...,item3を定義してHTML、JS内で参照してる箇所を改善してみました。item数が増減しても編集箇所は一箇所に抑えられ、MVCモデルの効果をより発揮できるかと思います。

    <!DOCTYPE HTML>
    <html>
    <head>
    <meta charset="utf-8">
    <script src="/jslib/angular.min.js"></script>
    <script>
    function MonaCtrl($scope){
    $scope.shippingMessage = "";
    $scope.shipping = 0;

    $scope.items = [
    { name : "黒あん最中", price : 100, amount : 0 },
    { name : "白あん最中", price : 120, amount : 0 },
    { name : "粒あん最中", price : 90, amount : 0 }
    ];

    $scope.getAmount = function(){
    var sum = 0;
    for(var i = 0; i < $scope.items.length; i++) {
    var item = $scope.items[i];
    sum += item.amount;
    }
    return sum;
    }
    $scope.getPayment = function(){
    var sum = 0;
    for(var i = 0; i < $scope.items.length; i++) {
    var item = $scope.items[i];
    sum += item.amount * item.price;
    }
    return sum;
    }
    $scope.getTotalcost = function(){
    return $scope.getPayment() + $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;
    }
    });
    }
    </script>
    </head>
    <body ng-app>
    <div ng-controller="MonaCtrl" >
    <ul>
    <li ng-repeat="item in items" >{{item.name}}:{{item.price}}円</li><br/>
    <li ng-repeat="item in items" >{{item.name}}購入:<input type="number" ng-model="item.amount" min="0" />個</input></li>
    <li>お買い上げ数:{{ getAmount() | number }}個</li>
    <li>購入代金:{{ getPayment() | number }}円 </li>
    <li>{{shippingMessage}}:{{shipping}}円</li>
    <li>総代金:{{getTotalcost() | number}}:円</li>
    </ul>
    </div>
    </body>
    </html>

  • 大塔

    ししまる様

    おお! ありがとうございます。
    確かにこの方が後々、保守性上がって良いですね!

コメントフォーム



captcha_key

アシアルの会社情報

アシアル株式会社はPHP、HTML5、JavaScriptに特化したWebエンジニアリング企業です。ユーザーエクスペリエンス設計から大規模システム構築まで、アシアルメンバーが各々の専門性を通じてインターネットの進化に貢献します。

会社情報詳細

最近の記事