AngularJS 入門 ~ ルーティング, フィルター


[「AngularJS 入門」](http://qiita.com/lga0503/items/d8efddcad2574e1938f1)の続きです。

今回はルーティングと、フィルターです。

# ルーティング

実は既にルーティングの設定はしてあるので見てみます。

```js:app/scripts/app.js
/* jshint indent: 4 */
'use strict';

angular
    .module('angularSampleApp', [
        'ngCookies', 'ngResource', 'ngSanitize', 'ngRoute'
    ])
    .config(function ($routeProvider) {
        // ルーティング設定
        $routeProvider
            .when('/', {
                templateUrl: 'views/main.html',
                controller: 'MainCtrl'
            })
            .otherwise({
                redirectTo: '/'
            });

    });
```
ルーティングはURLのマッチングパターンに対して、下記を設定するだけでOKです。

* コントローラー名
* ビューのパス

URLのマッチングパターンには __パラメーター__ を受け取るようにする事ができるので、新しく定義してみます。

## ルーティングを定義してみる

###`$routeProvider`にルーティング定義を追加

```js:app/scripts/app.js
/* jshint indent: 4 */
'use strict';

angular.module('angularSampleApp', [
        'ngCookies', 'ngResource', 'ngSanitize', 'ngRoute'
    ])
    .config(function ($routeProvider) {
        $routeProvider
            .when('/', {
                templateUrl: 'views/main.html',
                controller: 'MainCtrl'
            })
            // この部分を追加
            .when('/frameworks/:name', {
                templateUrl: 'views/desc.html',
                controller: 'DescCtrl'
            })
            .otherwise({
                redirectTo: '/'
            });
    });
```
URLのマッチングパターンに`:{変数名}`を使うと、パラメータとして受け取る事ができます。

### ルーティングに定義した`DescCtrl`を作る

```js:app/scripts/controllers/main.js
/* jshint indent: 4 */
'use strict';

angular.module('angularSampleApp')
    .controller('MainCtrl', function ($scope) {

        $scope.world = 'Angular';

        $scope.addFramework = function (text) {
            $scope.frameworks.push(text);
            $scope.world = '';
        };

    })
    // 新しく追加したコントローラー
    .controller('DescCtrl', function ($scope, $routeParams) {

        $scope.framework = {
            name: $routeParams.name
        };

    });
```

マッチしたルーティングのパラメータは、`$routeParams`サービスから取得できます。

> __補足__
> 引数の名前は`$routeParams`でなければなりません。
> 位置はどこでもいいのですが、DIの回に説明するのでここでは割愛

### ルーティングに定義した`app/views/desc.html`を作る

```html:app/views/desc.html
<pre>{{ framework }}</pre>
```
確認できればよいので変数のダンプ

## 準備ができたのでアクセスしてみます

ブラウザのURLを手動で`http://127.0.0.1:9000/#/frameworks/hoge`等にしてみるとイイ感じに表示されるはずです。
![kobito.1386187104.282593.png](https://qiita-image-store.s3.amazonaws.com/0/30739/bc944550-dc36-b740-22f4-3191458a7f02.png "kobito.1386187104.282593.png")

ルーティングが完成したので、次はリンクを貼ってみます。

## リンクからのアクセス

先程手動でやった事をマークアップするだけでできてしまいます。

```html:app/views/main.html
<p>
    <input type="text" ng-model="world">
    <span ng-click="addFramework(world)" class="btn btn-primary">Add</span>
</p>
<p>Hello {{ world }}!</p>

<p ng-init="frameworks = ['Backbone.js', 'Ember.js', 'Knockout.js']">
    <h3>{{ frameworks.length }} frameworks</h3>
    <ul>
        <!-- リンクに置き換えます -->
        <li ng-repeat="f in frameworks">
            <a ng-href="/#/frameworks/{{f}}">{{f}}</a>
        </li>
    </ul>
</p>
```
![kobito.1386189294.689709.png](https://qiita-image-store.s3.amazonaws.com/0/30739/a2ac0661-6fc8-30f5-a54d-6a9b0b52fc7d.png "kobito.1386189294.689709.png")

これでリンクが貼れます。

> __補足__
> 本来はパラメーターをurlエンコードする必要がありますが、ここでは省略しています。

# フィルター

データを変換する機構です。
ビュー上でフィルター専用のシンタックスがあるので、便利です。

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

ビュー上で`{{ 変数名|フィルター名 }}`と記述すると、変数をフィルタリングした結果が表示されます。
もちろん元の変数は不変です。

### 単純なフィルター

```app/views/main.html
<p>
    <input type="text" ng-model="world">
    <span ng-click="addFramework(world)" class="btn btn-primary">Add</span>
</p>
<p>Hello {{ world }}!</p>

<p ng-init="frameworks = ['Backbone.js', 'Ember.js', 'Knockout.js']">
    <h3>{{ frameworks.length }} frameworks</h3>
    <ul>
        <li ng-repeat="f in frameworks">
            <a ng-href="/#/frameworks/{{f}}">{{f|uppercase}}</a> <!-- 大文字にする -->
        </li>
    </ul>
</p>

```

![kobito.1386189955.911518.png](https://qiita-image-store.s3.amazonaws.com/0/30739/b877d06e-492a-d41e-078c-9b39c91d8c38.png "kobito.1386189955.911518.png")

`uppercase`フィルターで大文字が表示されました。

### オブジェクトに対するフィルター
フィルターが対応していれば、何に対しても使えます。

```app/views/main.html
<p>
    <input type="text" ng-model="world">
    <span ng-click="addFramework(world)" class="btn btn-primary">Add</span>
</p>
<p>Hello {{ world }}!</p>

<p ng-init="frameworks = ['Backbone.js', 'Ember.js', 'Knockout.js']">
    <h3>{{ frameworks.length }} frameworks</h3>
    <ul>
        <li ng-repeat="f in frameworks | filter:'a'"> <!-- 文字'a'が含まれている要素のみ表示 -->
            <a ng-href="/#/frameworks/{{f}}">{{f|uppercase}}</a>
        </li>
    </ul>
</p>
```
![kobito.1386190803.164149.png](https://qiita-image-store.s3.amazonaws.com/0/30739/ababf1a3-fef5-f24d-0640-9277c5f0e832.png "kobito.1386190803.164149.png")

`'a'`が含まれる`Backbone.js`だけが表示されました。

ここではリテラルを書きましたが、フィルターに渡す値に変数を使う事ができます。

## リアルタイムにフィルタリング

入力した値からリアルタイムにフィルタリングすることもできるのでやってみます。

```app/views/main.html
<p>
    <input type="text" ng-model="world">
    <span ng-click="addFramework(world)" class="btn btn-primary">Add</span>
</p>
<p>Hello {{ world }}!</p>

<p ng-init="frameworks = ['Backbone.js', 'Ember.js', 'Knockout.js']">
    <h3>{{ frameworks.length }} frameworks</h3>
    <input type="text" ng-model="searchText" /> <!-- 検索窓を設置 -->
    <ul>
        <li ng-repeat="f in frameworks | filter:searchText"> <!-- 変数によるフィルタリング -->
            <a ng-href="/#/frameworks/{{f}}">{{f|uppercase}}</a>
        </li>
    </ul>
</p>
```

![kobito.1386191332.955234.png](https://qiita-image-store.s3.amazonaws.com/0/30739/29ca3e6c-3b64-a856-0e39-b1d43dcc07cc.png "kobito.1386191332.955234.png")

バインディングやテンプレートがなかったらかなり面倒ですが、Angularだとここまで簡単になります笑

## フィルターの定義

フィルターの定義も簡単にできます。

```app/scripts/app.js
/* jshint indent: 4 */
'use strict';

angular.module('angularSampleApp', [
        'ngCookies', 'ngResource', 'ngSanitize', 'ngRoute'
    ])
    .config(function ($routeProvider) {
        $routeProvider
            .when('/', {
                templateUrl: 'views/main.html',
                controller: 'MainCtrl'
            })
            .when('/frameworks/:name', {
                templateUrl: 'views/desc.html',
                controller: 'DescCtrl'
            })
            .otherwise({
                redirectTo: '/'
            });
    })
    // フィルター `reverse` の定義
    .filter('reverse', function () {
        return function (values) {
            return values.concat().reverse();
        };
    });
```
`.filter()`にフィルター名と、 __どう変換するかの処理を記述したクロージャーを`return`するクロージャー__ を渡せば良いです。

> この例だと、ただ単に配列を逆順にする`reverse`フィルターです。


### 定義したフィルターを使ってみる

```html:app/views/main.html
<p>
    <input type="text" ng-model="world">
    <span ng-click="addFramework(world)" class="btn btn-primary">Add</span>
</p>
<p>Hello {{ world }}!</p>

<p ng-init="frameworks = ['Backbone.js', 'Ember.js', 'Knockout.js']">
    <h3>{{ frameworks.length }} frameworks</h3>
    <input type="text" ng-model="searchText" />
    <ul>
        <li ng-repeat="f in frameworks | reverse | filter:searchText"> <!-- reverseフィルタを追加 -->
            <a ng-href="/#/frameworks/{{f}}">{{f|uppercase}}</a>
        </li>
    </ul>
</p>
```
![kobito.1386193743.862862.png](https://qiita-image-store.s3.amazonaws.com/0/30739/642092ed-4a0f-eefa-d935-9e01eb0061e5.png "kobito.1386193743.862862.png")

逆順になりました！
使い方はビルドインのフィルタと同じですが、フィルターがネスト可能だと言う例も兼ねてます。

`frameworks|reverse|reverse`とすれば元に戻ります。

次回は、[AngularJS 入門 ~ サービス, DI](http://qiita.com/lga0503/items/e3ca6494b233db0eaefc)の予定です。