Top / AngularJS / TIPS集 / ng-repeatについて

AngularJSでよく出てくる、ng-repeat について。このdirective は

<ul ng-repeat="instance in ctrl.friends ">
  <li>{{instance.name}} </li>
</ul>

など、Controller がもつ配列を画面に表示するのによく使います。

やってみる

ソース全体

https://github.com/masatomix/ng-repeat-sample-web/releases/tag/0.0.2 からダウンロード可能です。

画面HTML

<table>
    <tr>
        <td>index</td>
        <td>id</td>
        <td>name</td>
        <td>age</td>
    </tr>
    <tr ng-repeat="instance in aboutCtrl.friends" >
        <td>{{$index}}</td>
        <td><display-value value='instance.id'/></td>
        <td><display-value value='instance.name'/></td>
        <td><display-value value='instance.age'/></td>
    </tr>
</table>

こんな感じです。

<tr ng-repeat="instance in aboutCtrl.friends" >

上記のように ng-repeatが使われています。aboutCtrl が保持する friends 配列をぐるぐると table に表示していきます。

aboutCtrl Controller

適当に割愛してますが、だいたい下記のような感じです。

angular.module('ngRepeatSampleWeb')
    .controller('AboutCtrl', function () {
        var me = this;
        me.init = function () {
            me.friends = [
                {name: 'John 25', age: 25, gender: 'boy', id: 5},
                {name: 'Jessie 30', age: 30, gender: 'girl', id: 4},
                {name: 'Johanna 28', age: 28, gender: 'girl', id: 3},
                {name: 'Joy 15', age: 15, gender: 'girl', id: 2},
                {name: 'Mary 28', age: 28, gender: 'girl', id: 6},
                {name: 'Peter 95', age: 95, gender: 'boy', id: 7},
                {name: 'Sebastian 50', age: 50, gender: 'boy', id: 1},
                {name: 'Erika 27', age: 27, gender: 'girl', id: 8},
                {name: 'Patrick 40', age: 40, gender: 'boy', id: 10},
                {name: 'Samantha 60', age: 60, gender: 'girl', id: 9}
            ];
        };
        me.init();
    });

配列オブジェクトを作って、friends フィールドに配列をセットしています。

Directive

画面HTML上に、個々のデータを表示するDirective:

<display-value value='instance.age'/>

が使われていますが、中身は下記のような感じです。

angular.module('ngRepeatSampleWeb')
   .directive('displayValue', function () {
       return {
           restrict: 'E',
           template: '[{{$ctrl.value}}]',
           scope:{},
           bindToController: {
               value: "="
           },
           controller: function () {},
           controllerAs: "$ctrl"
       };
   });

もらったデータにカッコをつけて、表示しているだけですね。

実行結果

実行してみましょう。コマンドは

npm install && bower install
grunt serve

などと叩けばOKだと思いますが、実行結果として

init.png

と、Controllerがもつデータが表示されました。

表示データを入れ替えてみる。

さてソース全体を見ると分かりますが、テーブル上部にはボタンがついていて、

<div>
  <button ng-click="aboutCtrl.search()">search</button>
  <button ng-click="aboutCtrl.init()">init</button>
</div>

とControllerのメソッドを呼び出すようになっています。Controllerに定義されたそれぞれのメソッド、search/init は以下のように定義されています。

// 初期化
me.init = function () {
     me.friends = [
         {name: 'John 25', age: 25, gender: 'boy', id: 5},
         {name: 'Jessie 30', age: 30, gender: 'girl', id: 4},
         {name: 'Johanna 28', age: 28, gender: 'girl', id: 3},
         {name: 'Joy 15', age: 15, gender: 'girl', id: 2},
         {name: 'Mary 28', age: 28, gender: 'girl', id: 6},
         {name: 'Peter 95', age: 95, gender: 'boy', id: 7},
         {name: 'Sebastian 50', age: 50, gender: 'boy', id: 1},
         {name: 'Erika 27', age: 27, gender: 'girl', id: 8},
         {name: 'Patrick 40', age: 40, gender: 'boy', id: 10},
         {name: 'Samantha 60', age: 60, gender: 'girl', id: 9}
     ];
 };

こちらは画面表示時にもよばれていました。friendsフィールドを配列で初期化しています。

// 中身入れ替え。
me.search = function () {
    me.friends = [
        {name: 'Peter 95', age: 95, gender: 'boy', id: 7},
        {name: 'Erika 27', age: 27, gender: 'girl', id: 8}
    ];
};

こちらも、friends フィールドの配列オブジェクトを入れ替えています。データ的には初期化に使ったオブジェクトとおなじプロパティ値を持つデータですが、オブジェクトなのであくまで別インスタンスであることにご注意です。

画面上のボタンを実行してみると、searchやinitをボタンを押すたびに、table上のデータが入れ替わるのが分かると思います。またこのソースは ngAnimate というangularJSのモジュールを適用しているため、フェードアウトしていくアニメーションがかかっていることが分かると思います。

アニメーションがヘン

ところでこのフェードアウトするアニメーションですが、searchで一部のデータに表示を絞ろうとすると、一瞬データが二行増えて、そこから表示が絞られていきます。 ほかにも、初期状態のままで initを押すと、いったん行が倍になってから元に戻ったり、なんかヘンです。

想定としては、初期状態からsearchを押すと、該当行だけが残されたまま、残りがフェードアウトしてくれたらな、、と思いますよね。

ng-repeatのオブジェクトの管理方法について。

さてココからが本題です。実は AngularJS は ng-repeat で画面上に 配列を展開する際、各行にIDを振って管理をしているようです。 そのIDは未指定時では、オブジェクトのハッシュ値*1 が使われるようです。

そのIDですが、今回のように表示に使っている配列やコレクションに追加・変更・削除があった場合、

といった動きをするようですね。要するに行をIDで管理してて、おなじIDの行に更新をかけにいくわけです。

さっきのアニメーションがヘンという話は init/searchメソッドで入れ替えられるオブジェクトは、 プロパティ値はおなじだけどあくまで別オブジェクトであるため、おなじIDのオブジェクトが元配列に存在しない結果、挿入 & 削除 ということがおこり、増えたり減ったりヘンな動きをしたわけですね。

実際、おなじオブジェクトを使うように、下記のように変えてみたところ、

var v7 ={name: 'Peter 95', age: 95, gender: 'boy', id: 7};
var v8 = {name: 'Erika 27', age: 27, gender: 'girl', id: 8};

me.init = function () {
    me.friends = [
        {name: 'John 25', age: 25, gender: 'boy', id: 5},
        {name: 'Jessie 30', age: 30, gender: 'girl', id: 4},
        {name: 'Johanna 28', age: 28, gender: 'girl', id: 3},
        {name: 'Joy 15', age: 15, gender: 'girl', id: 2},
        {name: 'Mary 28', age: 28, gender: 'girl', id: 6},
        v7,
        {name: 'Sebastian 50', age: 50, gender: 'boy', id: 1},
        v8,
        {name: 'Patrick 40', age: 40, gender: 'boy', id: 10},
        {name: 'Samantha 60', age: 60, gender: 'girl', id: 9}
    ];
};

// 中身入れ替え。
me.search = function () {
    me.friends = [
        v7,  // おなじオブジェクト
        v8  // おなじオブジェクト
    ];
};

該当行だけが残されたまま、残りがフェードアウトしていく想定通りのアニメーションが確認出来ました。適切に、おなじ行の更新とオブジェクトの削除だけが発生するようになったからですね。AngularJS、かしこいですねー。

track by xx のはなし

さてこのオブジェクトのハッシュ値を用いてDOMをトラッキング(追跡)する仕組みですが、上記のように参照を変えないことで、想定通りの動きが実現できました。 しかし、たとえばRESTの戻り値で画面を更新かける場合など「論理的には同じオブジェクトだけど、参照は別になっちゃう」なんてケースもちょくちょくあります。AngularJSではそのために「トラッキングはこの値を使ってやるよ」という指定方法があります。ときどき見かける track by xx ってヤツです。具体的には下記の通り。

修正前:<tr ng-repeat="instance in aboutCtrl.friends" >
修正後:<tr ng-repeat="instance in aboutCtrl.friends track by instance.id" >

これで「(更新をかけにいく行を指定するために) 元データを追跡する際には instance変数のidというプロパティを使うよ」という意味になります。 上記の例では、ハッシュ値が異なっていてもidプロパティが等しいなら同じとみなしてその行を更新したいので、まさに上記の指定で想定通りの動きをさせることができます。

トラッキング指定に関数を指定する

書き途中

蛇足。track by $indexで重複回避の件

書き途中

まとめ

書き途中

関連リンク


この記事は

選択肢 投票
おもしろかった 0  
そうでもない 0  

Top / AngularJS / TIPS集 / ng-repeatについて

現在のアクセス:3878


*1 $id(instance)で取れるらしい

トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS