AngularJSで配列とCheckBoxをバインディングする。

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

AngularJSを用いている際に、以下のようなデータ構造をCheckBoxにバインディングする際に非常に悩みました。
単純にAPIから配列をもらいバインディングさせたいのですが、
ただバインディングさせるのではうまく行かないのでそのとき行った方法を紹介します。

Fruits

ID Name
1 リンゴ
2 バナナ
3 グレープ
4 オレンジ
5 ピーチ

User

ID Name LikeFruits
1 一郎 1
2 次郎 2
3 三郎 3
4 四郎 4
5 五郎 5

プログラムの要件としては以下の通りです。

  • APIから上記のFruitsとUserを取得した結果に基づきビューを生成する。
  • User内のLikeFruitsはFruitsをマスタとしてチェックボックスを用いて更新する。
  • Userを受信した際にLikeFruitsに含まれていた値はチェック済みとする。

実装例

チェックボックスとのバインディング実装した例が以下になります。

Array Binding CheckBox

挙動としては以下のようになります。

チェック前

スクリーンショット 2014-06-24 12.38.52

チェック後

スクリーンショット 2014-06-24 12.39.11

チェックボックスと配列をバインディングする際に意識する点は二つあります。

一つはチェック状態をどのように評価するか。
もう一つはチェックのイベントでの挙動をどのようにするかです。

チェックの評価

今回の例ではチェックの評価は以下のように行っています。

<ul ng-repeat="(fruitId,fruitName) in fruits">
    <li>
        <input type="checkbox" id=person_like_fruit_{{person.id}}_{{fruitId}} 
        	ng-checked="person.likeFruits.indexOf(fruitId) > -1" 
        	ng-click="toggleCheck(person.id,fruitId)">
        <label for="person_like_fruit_{{person.id}}_{{fruitId}}">{{fruitName}}</label>
    </li>
</ul>

4行目のng-checkedの箇所が該当箇所となります。

バインディングしたperson.likeFruitsにチェック対象のfruitIdの有無によってチェックの判断を行っております。

チェックのイベントでの挙動

チェックのイベントでの挙動は以下のようになっております。

<ul ng-repeat="(fruitId,fruitName) in fruits">
    <li>
        <input type="checkbox" id=person_like_fruit_{{person.id}}_{{fruitId}} 
        	ng-checked="person.likeFruits.indexOf(fruitId) > -1" 
        	ng-click="toggleCheck(person.id,fruitId)">
        <label for="person_like_fruit_{{person.id}}_{{fruitId}}">{{fruitName}}</label>
    </li>
</ul>
$scope.toggleCheck = function (targetPersonId, fruitId)
{
    var targetPersonLikeFruits, idx;
    angular.forEach($scope.people, function (person)
    {
        if (angular.equals(person.id, targetPersonId)) {
            targetPersonLikeFruits = person.likeFruits;
        }
    });
    idx = targetPersonLikeFruits.indexOf(fruitId);
    if (angular.equals(idx, - 1)) {
        targetPersonLikeFruits.push(fruitId);
    }
    else {
        targetPersonLikeFruits.splice(idx, 1);
    }
};

JavaScript内ではViewから渡された対象のUserのIDをもとにModel内の対象のLikeFruitsを探します。

対象のLikeFruits内に、Viewから渡されたfruitIdの有無を評価します。
fruitIdが配列内に無ければ追加を行い、有れば配列から取り除きます。

ng-repeatを用いた連想配列走査

本題とは外れるのですが、今回の例で用いたので紹介します。

今回FruitsはAPIからIDをキーとする連想配列で受信した事を想定しています。

$scope.fruits = {
    '1' : 'リンゴ', '2' : 'バナナ', '3' : 'グレープ', '4' : 'オレンジ', '5' : 'ピーチ' 
};

このような連想配列をng-repeatでkeyとvalueともに取得するには以下のように記載する必要が有ります。

<ul ng-repeat="(fruitId,fruitName) in fruits">
    <li>
        <input type="checkbox" id=person_like_fruit_{{person.id}}_{{fruitId}} 
        	ng-checked="person.likeFruits.indexOf(fruitId) > -1" 
        	ng-click="toggleCheck(person.id,fruitId)">
        <label for="person_like_fruit_{{person.id}}_{{fruitId}}">{{fruitName}}</label>
    </li>
</ul>

上記のように記載する事で、連想配列のKeyとValueともに取得する事が可能となります。

まとめ

作例の用な要件を実装しようとした際に、はじめAPIとのインターフェースが非常に煩雑になっておりました。
しかし、ネットワーク側に煩雑なデータを送るのはネットワークのパフォーマンス的にも良くないと考え、
データ量を減らす為上記の実装を行いました。

参考サイト