Angular.js入門 (3)フィルタ

前回: [Angular.js入門 (2)コントローラ](http://qiita.com/_rdtr/items/715a3d13e637e5daaa0b)
次回: [Angular.js入門 (4)ディレクティブ その1](http://qiita.com/_rdtr/items/5edffeb96a9663fa1b1c)

前回ユーザが入力した文字列を反転させるのに, コントローラ内でメソッドを定義して適用してやるという方法を練習しました。
しかしAngular.jsにはフィルタという, モデルのデータに何らかの定形処理を施すのに適したやり方が提供されています。

## フィルタを使ってみる

前回と同じく文字列を反転させてやりましょう。

```index.html
<!doctype html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Angular.jsのテスト</title>
    <link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/foundation/5.2.2/css/foundation.css">
</head>
<body>

<div ng-app="myApp">
    <div ng-controller="FirstCtrl">
        <input type="text" ng-model="data.message">
        <div>{{data.message}}</div>
    </div>

    <div ng-controller="SecondCtrl">
        <input type="text" ng-model="data.message">
        <div>{{data.message|reverse}}</div>
    </div>
</div>

</body>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.6/angular.js"></script>
<script type="text/javascript" src="main.js"></script>
</html>
```
```main.js
var myApp = angular.module('myApp', []);
myApp.factory('Data', function(){
    return {message: "これはテストです"}
})

myApp.filter('reverse', function() {
    return function(text) {
        return text.split("").reverse().join("");
    }
})

function FirstCtrl ($scope, Data) {
    $scope.data = Data;
}

function SecondCtrl ($scope, Data) {
    $scope.data = Data;
}
```

myAppにfilterというメソッドを生やしてその中でフィルタ名"reverse"と具体的な処理(前回と同じ)を自分で定義できます。filterメソッドの引数となっている無名関数の中で, さらに処理をしたい関数を返すようにするのに注意ですね。

定義してやったフィルタは, 
{{data.message|reverse}}
のようにパイプ形式で記述することで適用できます。

![フィルタの適用](https://qiita-image-store.s3.amazonaws.com/0/20470/fb4a8068-4dda-52cd-eb60-0a79c31d6930.png)

コントローラ内の文字列反転メソッドは消してしまいましたが, 入力した文字を反転させるフィルタは効いていることがわかります。
また前回myAppのfactoryメソッドを通じてDataというコントローラ間で値を共有するためのサービスを定義しましたが, これはフィルタでも使うことができます。

```main.js
var myApp = angular.module('myApp', []);
myApp.factory('Data', function(){
    return {message: "これはテストです"}
})

myApp.filter('reverse', function(Data) {
    return function(text){
        return text.split("").reverse().join("") + ' | ' + Data.message;
    };
})

function FirstCtrl ($scope, Data) {
    $scope.data = Data;
}

function SecondCtrl ($scope, Data) {
    $scope.data = Data;
}
```

filterメソッドの引数となっている無名関数のパラメータとしてDataを指定していることで, その値が実際に使えていることが分かります。
反転した文字列 に | を挟んで 反転前のオリジナルの文字列を加えて表示したものが2つ目のdiv要素として表示されます。

![双方向バインディング](https://qiita-image-store.s3.amazonaws.com/0/20470/2239e164-2825-dad8-8ac3-62344f0705b2.png)


適当にフォームに文字を入力すると, Data.messageの内容もそれに追随して変わっていくことに注目ですね。モデルを変更するとUIに影響があるだけではなく, UI側の変更でモデルの値も変わるというAngularの"双方向"バインディングの威力が分かるかと思います。
（知らないで見るとfactoryで'これはテストです'と定義したあと変更を加えていないはずのDataの値がなぜ変更されるのか…？と混乱してしまうと思いますので, そこらへんはAngularが裏で面倒を見てくれていることを忘れないようにしましょう）

## ngRepeat
モデルは時として同じようなデータをいくつも扱うため, UI上もリストやテーブルで同じ記述を繰り返すことが多いです。Angularではこれらを扱いやすくするためのngRepeatと言う強力な機構を用意してくれています。

例を見た方が早いです。今回は
https://egghead.io/lessons/angularjs-ngrepeat-and-filtering-data
こちらのデータをそのまま使わせてもらっています。

```js:main.js
var myApp = angular.module('myApp', []);

myApp.factory('Avengers', function() {
    var Avengers = {};
    Avengers.cast = [
      {
        name: "Robert Downey Jr.",
        character: "Tony Stark / Iron Man"
      },
      {
        name: "Chris Evans",
        character: "Steve Rogers / Captain America"
      },
      {
        name: "Mark Ruffalo",
        character: "Bruce Banner / The Hulk"
      },
      {
        name: "Chris Hemsworth",
        character: "Thor"
      },
      {
        name: "Scarlett Johansson",
        character: "Natasha Romanoff / Black Widow"
      },
      {
        name: "Jeremy Renner",
        character: "Clint Barton / Hawkeye"
      },
      {
        name: "Tom Hiddleston",
        character: "Loki"
      },
      {
        name: "Clark Gregg",
        character: "Agent Phil Coulson"
      },
      {
        name: "Cobie Smulders",
        character: "Agent Maria Hill"
      },
      {
        name: "Stellan Skarsgard",
        character: "Selvig"
      },
      {
        name: "Samuel L. Jackson",
        character: "Nick Fury"
      },
      {
        name: "Gwyneth Paltrow",
        character: "Pepper Potts"
      },
      {
        name: "Paul Bettany",
        character: "Jarvis (voice)"
      },
      {
        name: "Alexis Denisof",
        character: "The Other"
      },
      {
        name: "Tina Benko",
        character: "NASA Scientist"
      }
    ];
    return Avengers;
})

function AvengersCtrl ($scope, Avengers) {
    $scope.avengers = Avengers;
}
```

これまでの内容はmain.jsから消して新たにグローバルなデータとしてAvengersを登録し, castプロパティに映画アベンジャーズのキャスト一覧をオブジェクトの配列として格納しているだけです。

続いてhtmlを以下のように変えてみます。

```html:index.html
<!doctype html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Angular.jsのテスト</title>
    <link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/foundation/5.2.2/css/foundation.css">
</head>
<body>

<div ng-app="myApp">
    <div ng-controller="AvengersCtrl">
        <table>
            <tr ng-repeat="actor in avengers.cast">
            <td>{{actor.name}}</td>
            <td>{{actor.character}}</td>
            </tr>
        </table>
    </div>
</div>

</body>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.6/angular.js"></script>
<script type="text/javascript" src="main.js"></script>
</html>
```
ng-repeat属性の記述に注目してください。やりたいことは想像がつくかと思いますが, 
ng-repeat="actor in avengers.cast"
でavengersのcast内の要素を１つずつループを回して処理が可能です。変数名としてactorを指定しているので, 続く

<td>{{actor.name}}</td>
<td>{{actor.character}}</td>

でcastの各要素のnameプロパティとcharacterプロパティの値が展開されてテーブルを表示するというわけですね。

![ng-Repeat](https://qiita-image-store.s3.amazonaws.com/0/20470/64877d54-1b6b-a8be-930e-faab108f6b41.png)

(・∀・)ｲｲ!!

## ng-Repeat×フィルタ
ng-Repeatはさらにフィルタと合わせて使うこともできます。

htmlの方に検索フォームを追加してみましょう。

```html:index.html
<!doctype html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Angular.jsのテスト</title>
    <link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/foundation/5.2.2/css/foundation.css">
</head>
<body>

<div ng-app="myApp">
    <div ng-controller="AvengersCtrl">
        <input type="text" ng-model="searchText">
        <table>
            <tr ng-repeat="actor in avengers.cast | filter:searchText">
            <td>{{actor.name}}</td>
            <td>{{actor.character}}</td>
            </tr>
        </table>
    </div>
</div>

</body>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.6/angular.js"></script>
<script type="text/javascript" src="main.js"></script>
</html>
```

テーブルの上にinputタグを挿入して, ここに入力する値をsearchTextというモデルと紐付けています。さらにng-repeatの記述で filter:searchTextという処理にパイプしています。

最初の例では自分で定義したreverseというフィルタを適用しましたが, 今回の"filter"という名前のフィルタはAngularの用意してくれている標準のフィルタです。これに:で文字列を渡すと, その文字列を含むデータでフィルタをかけてくれるという。

まさか本当にそんな便利な機能が...

![ng-Repeat*フィルタ](https://qiita-image-store.s3.amazonaws.com/0/20470/8d376e8e-ea5d-d256-18ac-b4ff7939f542.png)

ありました。
さらにプロパティにはnameとcharacterがありますが, nameだけでフィルタをかけたいといったこともできます。

```html:index.html
<!doctype html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Angular.jsのテスト</title>
    <link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/foundation/5.2.2/css/foundation.css">
</head>
<body>

<div ng-app="myApp">
    <div ng-controller="AvengersCtrl">
        <input type="text" ng-model="searchText.name">
        <table>
            <tr ng-repeat="actor in avengers.cast | filter:searchText">
            <td>{{actor.name}}</td>
            <td>{{actor.character}}</td>
            </tr>
        </table>
    </div>
</div>

</body>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.6/angular.js"></script>
<script type="text/javascript" src="main.js"></script>
</html>
```

入力欄のng-modelの値を変更しています。.nameと付けてやるだけで, 

![ng-Repeat*フィルタ2](https://qiita-image-store.s3.amazonaws.com/0/20470/e17c090b-6e1f-e03b-9d1b-18f07dcc4e69.png)

Tony Starkはcharacterプロパティなので, tonまで入力した段階で既に検索に引っかからなくなっているのが分かります。もちろんsearchText.characterも同様に動きます。

また特別なプロパティとして"$"があり,

```html 
<input type="text" ng-model="searchText.$">
```
のように書くと「オブジェクトの全てのプロパティをフィルタの対象にする」ということを明示的に指定できます。（今回の場合, 結果はsearchTextとだけ書いた時と同じです）

## 標準フィルタいろいろ
Angularには他にも標準で便利なフィルタが色々用意されているので, いくつか見ていきます。

### orderBy

```html:index.html
<!doctype html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Angular.jsのテスト</title>
    <link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/foundation/5.2.2/css/foundation.css">
</head>
<body>

<div ng-app="myApp">
    <div ng-controller="AvengersCtrl">
        <input type="text" ng-model="searchText.$">
        <table>
            <tr ng-repeat="actor in avengers.cast | orderBy: 'name'">
            <td>{{actor.name}}</td>
            <td>{{actor.character}}</td>
            </tr>
        </table>
    </div>
</div>

</body>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.6/angular.js"></script>
<script type="text/javascript" src="main.js"></script>
</html>
```

orderByフィルタを適用すると, 

![orderByフィルタ](https://qiita-image-store.s3.amazonaws.com/0/20470/273b3e77-772f-180a-5220-f7f17518ce81.png)

指定したnameプロパティ順にソートが行われます。

```
<tr ng-repeat="actor in avengers.cast | orderBy: '-name'">
```
のように頭にマイナスをつけてやると

![orderBy逆順](https://qiita-image-store.s3.amazonaws.com/0/20470/305d21de-26a8-9e7e-e941-53d400cd8b9a.png)

逆順ソートだってできるんです。

### limitTo

```html
<tr ng-repeat="actor in avengers.cast | limitTo: 5">
```

としてやると
![limitTo](https://qiita-image-store.s3.amazonaws.com/0/20470/07dba1d7-aa08-017a-03d7-15bad713829b.png)

上限数を制限できます。これも

```html
<tr ng-repeat="actor in avengers.cast | limitTo: -5">
```

とマイナスを付けてやると「最後から5個」にすることができます。

### フィルタの連結

```html
<tr ng-repeat="actor in avengers.cast | orderBy: 'name' | limitTo : -5">
```

のようにパイプを連結することで複数のフィルタを順次適用可能です。
この場合は名前順にソートしたうえで最後から5個を表示, となりますね。

![フィルタの連結](https://qiita-image-store.s3.amazonaws.com/0/20470/fc5a1413-4e3f-e39a-7360-a567a57b0052.png)

### lowercase / uppercase
小技ですが, 今度はテーブルの各要素を

```html
<td>{{actor.name | lowercase}}</td>
<td>{{actor.character | uppercase}}</td>
```
のように変更してみます。

![lowercase/uppercase](https://qiita-image-store.s3.amazonaws.com/0/20470/93104771-5cb5-4938-6650-47bd23450bf3.png)

大文字, 小文字も簡単に変換できますね。

標準で用意されているフィルタはドキュメントにも記載があるので
https://docs.angularjs.org/api/ng/filter
一度目を通しておくと良いかもです。