AngularJS 入門 - Directive


前回の続きです。

今回は`Directive`（ディレクティブ）についてです。

`ngModel`や`ngRepeat`などを既に使っているのでディレクティブがどんなものかは大体分かっているかと思います。

ビルドインのディレクティブ一覧 => http://docs.angularjs.org/api/ng#directive

####なので早速ですがディレクティブを作ります。

## 単純なディレクティブ
レイアウトを持たず、動作を追加するディレクティブを作ってみます。

ちょうどTwitter Bootstrapが雛形に使われているので、リンクにツールチップを追加します。

```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>

<div>
    <h3>{{ frameworks.length }} frameworks</h3>
    <input type="text" ng-model="searchText" />
    <span ng-hide="frameworks">読み込み中...</span>
    <ul>
        <li ng-repeat="f in frameworks | reverse | filter:searchText">
            <!— tooltip属性を追加する —>
            <a ng-href="/#/frameworks/{{f}}" tooltip>{{f|uppercase}}</a>
        </li>
    </ul>
</div>
```

`tooltip`ディレクティブを定義します。

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

angular.module('angularSampleApp', [
        'ngCookies', 'ngResource', 'ngSanitize', 'ngRoute'
    ])
    .factory(‘…’, function () {})
    // tooltip ディレクティブを定義
    .directive('tooltip', function () {
        return function (scope, element, attr) {

            element.tooltip({
                title: 'tooltip test'
            });

        };
    });
```

省略型はクロージャを返却して定義とします。
この場合は`tooltip`ディレクティブが設定されているDOMに対して、ツールチップを表示するだけです。

> `scope` `element` `attr` は インジェクターからインジェクトされた物ではないです。

![名称未設定-2.png](https://qiita-image-store.s3.amazonaws.com/0/30739/23a597a9-9e1a-99a5-68e0-d568732ef5ee.png "名称未設定-2.png")

`element`はただのjQueryオブジェクトなので、DOM操作はなんでもできますね！

## ディレクティブにパラメータを渡す

先ほど作ったディレクティブを少し弄ります。

```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>

<div>
    <h3>{{ frameworks.length }} frameworks</h3>
    <input type="text" ng-model="searchText" />
    <span ng-hide="frameworks">読み込み中...</span>
    <ul>
        <li ng-repeat="f in frameworks | reverse | filter:searchText">
            <!— ディレクティブに変数を渡す —>
            <a ng-href="/#/frameworks/{{f}}" tooltip="f">{{f|uppercase}}</a>
        </li>
    </ul>
</div>
```

`tooltip`ディレクティブに変数`f`を渡す。

```js:app/scripts/app.js

    .directive('tooltip', function ($parse) { // expressionを評価するためのサービス
        // オプションを指定する場合はオブジェクトを返す
        return {
            restrict: ‘A’,
            link: function (scope, element, attr) {

                // attr.tooltip=“f” を指定したので、fの中身を取得し、ツールチップに設定
                // scope.$eval(attr.tooltip) でもよい
                var value = $parse(attr.tooltip)(scope);

                element.tooltip({
                    title: value
                });

            }
        };
    });
```
`attr.tooltip`の中身はただの文字なので、評価した結果をツールチップに渡します。

![名称未設定-1.png](https://qiita-image-store.s3.amazonaws.com/0/30739/61aa4dd7-ad42-c34d-e51a-f5f2933edb73.png "名称未設定-1.png")

値の変更を監視してjsを実行したい場合は、`scope.$watch()` を使えばよいです。
http://docs.angularjs.org/api/ng.$rootScope.Scope#methods_$watch

#### restrict?

ディレクティブの`restrict`はディレクティブの対象を制限する為に使います。

* __E__ &lt;some-directive&gt;&lt;/some-directive&gt;
* __A__ &lt;div some-directive=“”&gt;&lt;/div&gt;
* __C__ &lt;div class=“some-directive”&gt;&lt;/div&gt;
* __M__ &lt;!-— directive: some-directive -—&gt;

複数指定する場合は `restrict: ‘AEC’` の様に繋げて書きます。

## テンプレート

ディレクティブにはテンプレートの機能があります。

```app/scripts/app.js

    .directive('tooltip', function ($parse) {
        return {
            restrict: 'A',
            // テンプレートを追加
            template: '<span class="glyphicon glyphicon-asterisk"></span>',
            link: function (scope, element, attr) {

                var value = $parse(attr.tooltip)(scope);

                element.tooltip({
                    title: value
                });

            }
        };
    });
```

![名称未設定-1.png](https://qiita-image-store.s3.amazonaws.com/0/30739/76f8fda8-32ac-aa8e-7023-772233b9c4a0.png "名称未設定-1.png")

一応これだけでテンプレートが反映されますが、固定文ではダメなので`ngTransclude`を使います。

### ngTransclude

```js:app/scripts/app.js

    .directive('tooltip', function ($parse) {
        return {
            restrict: 'A',
            // テンプレートを編集
            template: '<span class="glyphicon glyphicon-asterisk"></span> <i ng-transclude></i>',
            // transcludeを設定
            transclude: true,
            link: function (scope, element, attr) {

                var value = $parse(attr.tooltip)(scope);

                element.tooltip({
                    title: value
                });

            }
        };
    });
```

![名称未設定-2.png](https://qiita-image-store.s3.amazonaws.com/0/30739/9a2310b5-8037-c470-895a-ec7b62b23be1.png "名称未設定-2.png")


テンプレートのどこに内容をセットするかを`ngTransclude`で指定するだけなので簡単です。

## ディレクティブ間の連携
公式ドキュメント（[http://docs.angularjs.org/guide/directive#creating-custom-directives_demo_creating-directives-that-communicate](http://docs.angularjs.org/guide/directive#creating-custom-directives_demo_creating-directives-that-communicate)）にいい感じの説明があるので、今回は省略します。