AngularJSアプリケーション開発ガイド 第2章 AngularJSアプリケーションの構造 その2

AngularJSアプリケーション開発ガイド

CSSのクラスとスタイル

ng-class・ng-styleで、条件に応じたCSSを適用できる。Angular式が以下のようになる必要がある。

  • クラス名を指定した文字列
  • クラス名の配列
  • クラス名と真偽値を対応付ける連想配列
<!DOCTYPE html>
<html ng-app>
<head>
    <meta charset="utf-8">
    <title></title>
    <style type="text/css">
        .selected {
            background-color: lightgreen;
        }
    </style>
</head>
<body>
<table ng-controller="RestaurantTableController">
    <tr ng-repeat="restaurant in directory" ng-click="selectRestaurant($index)"
        ng-class="{selected: $index==selectedRow}">
        <td ng-bind="restaurant.name"></td>
        <td ng-bind="restaurant.cuisine"></td>
    </tr>
</table>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.27/angular.min.js"></script>
<script>
    function RestaurantTableController($scope) {
        $scope.directory = [
            {name: "吉野家", cuisine: "牛丼"},
            {name: "マクドナルド", cuisine: "ハンバーガー"},
            {name: "サイゼリヤ", cuisine: "イタリアン"}
        ];

        $scope.selectRestaurant = function (row) {
            $scope.selectedRow = row;
        }
    }
</script>
</body>
</html>

src属性とhref属性

img要素のsrc属性や、a要素のhref属性を動的に書き換えたい場合、ng-src/ng-hrefを使用する。

<img ng-src="/images/cats/{{favoriteCat}}">
<a ng-href="/shop/category={{numberOfBalloons}}">リンク文字列</a>

Angular式

テンプレートでのAngular式は、テンプレートとアプリケーションロジック、及びデータとを接続し、同時にアプリケーションロジックがテンプレート内に進入するのを防ぐことを意図している。

Angular式では、四則演算、比較、論理演算、ビット演算、$scopeで公開されている関数の呼び出し、配列の要素やオブジェクトのプロパティへのアクセスができる。ただし、javaScriptの式と等価ではない(ループ・フロー制御・データの変更を伴う演算などには対応していない)。

コントローラによるUIの責任の分離

アプリケーションの中でコントローラは3つの役割を持っている。

  1. アプリケーションのモデルを初期状態にセットアップする
  2. $scopeを通じ、モデルと関数をビューに公開する
  3. モデルへの変更を監視し、適切に応答する

コントローラの本質的な意義は、ユーザーがビューとのインタラクションを通じて行おうとしていることを実現するという点にある。コントローラを小さくかつ管理の容易なものにするために、ビューの中で機能を持った単位ごとに1つずつコントローラを用意することが推奨されている。

UIの構造が複雑な場合は、コントローラを入れ子状に定義できる。入れ子の内側のコントローラに渡される$scopeは、外側のコントローラの$scopeを継承している。

$scopeを通じたモデルのデータの公開

コントローラに渡される$scopeオブジェクトは、モデルのデータをビューに公開するための仕組みでもある。AngularJSでは、$scopeの中でアクセスできるプロパティだけがモデルのデータとみなされる。

ng-modelで指定されたモデルはコントローラの$scopeの中で解釈される。同時に、ここでは入力フィールドの状態と指定されたモデルとの間で双方向のデータバインディングが定義される。

$watchを使ったモデルの変化への監視

$scope.$watchは、モデルのデータが変化した場合に通知を受け取るために使う。プロパティとしてアクセス可能なものや、関数によって計算可能なものであれば何でも監視できる。$watchのシグネチャは以下のようになっている。

$watch(watchFn, watchAction, deepWatch)

watchFn:
Angular式の文字列か、監視対象の現在の値を返す関数を指定する。これらは何度も実行されるため、実行による副作用がないようにする必要がある。
watchAction:
watchFnで指定した値が変化した場合に、呼び出される関数またはAngular式。
deepWatch:
trueの場合、対象のオブジェクトが持つそれぞれのプロパティについて変更の有無がチェックされる。

$watchは、通知を受け取る必要がなくなった場合に監視を解除するための関数を返す。

以下は、値引き機能付きショッピングカートのサンプル(書籍のサンプルに手を加えていて、angular-locale_jaを追加して、currencyフィルタで円記号が表示されるようにしていたり、for文でtotalを計算していたのをreduceを使ったりしている)。

<!DOCTYPE html>
<html ng-app>
<head>
    <meta charset="utf-8">
    <title></title>
</head>
<body>
<div ng-controller="CartController">
    <div ng-repeat="item in items">
        <span ng-bind="item.title"></span>
        <input ng-model="item.quantity">
        <span ng-bind="item.price | currency"></span>
        <span ng-bind="item.price * item.quantity"></span>
    </div>
    <div>小計:<span ng-bind="bill.total | currency"></span></div>
    <div>値引き:<span ng-bind="bill.discount | currency"></span></div>
    <div>合計:<span ng-bind="bill.subtotal | currency"></span></div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.27/angular.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-i18n/1.2.15/angular-locale_ja.js"></script>
<script>
    function CartController($scope) {
        $scope.bill = {};

        $scope.items = [
            {title: "AngularJSアプリケーション開発ガイド", quantity: 1, price: 2592},
            {title: "AngularJSリファレンス", quantity: 1, price: 4104},
            {title: "Webアプリ構築のためのAngularJS", quantity: 1, price: 2070}
        ];

        var calculateTotals = function () {
            var total = $scope.items.reduce(function (previousValue, currentValue) {
                return previousValue + (currentValue.price * currentValue.quantity);
            }, 0);

            $scope.bill.total = total;
            $scope.bill.discount = total > 10000 ? 1000 : 0;
            $scope.bill.subtotal = total - $scope.bill.discount;
        };

        $scope.$watch($scope.totalCart, calculateTotals);
    }
</script>
</body>
</html>

$watchのパフォーマンス

$watchが頻繁に呼ばれると、パフォーマンス上の問題を引き起こすことがある。

複数の対象の監視

複数のプロパティを監視し、そのいずれかが変化した場合に関数を呼び出すには、以下の2つの方法がある。

  1. プロパティの値を連結したものを監視する
  2. 監視対象を含む配列又はオブジェクトを定義し、deepWatchにtrueを指定してこれを監視する
// 1.の場合
$scope.$watch("things.a + things.b", function(){...});
// 2.の場合
$scope.$watch("things", function(){...}, true);

今回はここまで。次回は「2.4 モジュールを使った依存関係の管理」から。

コメントをどうぞ

コメントを残す