Angular.js入門 (4)ディレクティブ その1

前回: [Angular.js入門 (3)フィルタ](http://qiita.com/_rdtr/items/5bf69a29e33f668168cc)
次回: [Angular.js入門 (5)ディレクティブ その2](http://qiita.com/_rdtr/items/590ccb7be16dc8ed9be3)

今回はいよいよangular.jsの肝とも言えるdirectiveについてです。ディレクティブという単語自体が日本語として馴染みが無いのもありますし, 実際になかなか複雑な機能なので理解が進まないところもあります(^ω^;)

filterと同様自分でカスタムのdirectiveを作ることもできるのですが, Angular自身も標準directiveを持っています。まずはこちらを使って何ができるのかから見て行きましょう。

## はじめてのDirective

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

app.directive("test", function () {
    return {
        restrict: "E",
        template: "<div>これはテストだよ！</div>"
    }
})
```

まずはjavascript側から。
appにdirectiveを生やしてdirective名のtestと関数をパラメータに渡しています。関数はrestrictとtemplateのプロパティを持つオブジェクトを返却します。

```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">
    <test></test>
</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>
```

続いてhtml側でng-app内に, js側で指定したdirective名"test"という名前のタグを書いてみます。ブラウザでindex.htmlを開くと

![はじめてのDirective](https://qiita-image-store.s3.amazonaws.com/0/20470/53db95cf-28e2-4396-1bb5-46bea9a21bdc.png)

testタグの中にtemplateとして指定したコンテンツが入っているのが分かります。

Directiveは「指令」「命令」と言った意味がありますが, まさにあるDOM要素を操るためにjQueryとは全く異なる方法で記述する命令なんだとイメージしておくといいかと思います。

今回のケースではrestrictの"E"はelementを指していて,

* "test"という名前の要素("E")を持つDOMは
* テンプレートとして"\<div\>これはテストだよ！\</div\>"というテンプレートを使え！
という命令になるわけです。

他の例も見てみましょう。

```main.js
var app = angular.module("myApp", []);

app.directive("test", function () {
    return {
        restrict: "A",
        link: function () {
            alert('これはテストです！');
        }
    };
});
```

今度はrestrictは"A", 属性です。linkというのはクリックなど色々なイベントリスナーを割り当てるのに良く使うのですが, 今回はalertを出力する関数を紐付けているくらいにとらえていて大丈夫です。

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

属性縛りなので\<div test\>とtestを属性に変更していることに注目してください。ブラウザを開くと

![はじめてのDirective2](https://qiita-image-store.s3.amazonaws.com/0/20470/28cccb32-8fe0-9922-3c08-6e9c9017317e.png)

アラートが表示されます。

もう1つ。

```js:main.js(抜粋)
app.directive("test", function () {
    return {
        restrict: "C",
        link: function () {
            alert('これはテストです！');
        }
    };
});
```

今度はC, クラス縛りなので

```html:index.html(抜粋)
<div ng-app="myApp">
   <div class="test"></div>
</div>
```

と書くと同じくアラートが表示されます。

restrictプロパティを省略するとA, 属性縛りがデフォルトなので適用されます。Aの使用が一番多くなるだろうことが想定されているようですね。

## 実用的なDirectiveの例

上でlinkはイベントリスナを書くのに使うと言ったのを見てみましょう。

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

app.directive("enter", function () {
    return {
        restrict: "A",
        link: function (scope, element) {
            element.bind("mouseenter", function () {
                console.log('マウスin');
            });
        }
    };
});
```

今度のlinkで指定している関数はscopeとelementの引数を取っています。scopeについてはとりあえず飛ばしてください。elementはそのままで, ここで指定している"enter"という属性がつく要素の"mouseenter"イベントにconsole.log出力関数をバインドしていることになります。

```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 enter>この上にマウスを乗せてみる。</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>
```

htmlの方も対応するようにenter属性のdivを作っています。
実際にマウスをテキストの上に乗せてみて, consoleが出力されていることが確認できます。

![マウスオーバー](https://qiita-image-store.s3.amazonaws.com/0/20470/61b1cc38-4d53-f9d9-43a6-61049d419770.png)

こうしたイベントに紐づく処理を書くのは非常に多いユースケースなので, もう少し簡単にlinkを書く方法も用意されています。
まず, restrictはAがデフォルトなので省略します。

```main.js
var app = angular.module("myApp", []);

app.directive("enter", function () {
    return {
        link: function (scope, element) {
            element.bind("mouseenter", function () {
                console.log('マウスin');
            });
        }
    };
});
```

このようにlink以外に指定するものが無いときは, linkを書かずに直接functionを返却しても同じ動きになります。

```main.js
var app = angular.module("myApp", []);

app.directive("enter", function () {
    return function (scope, element) {
        element.bind("mouseenter", function () {
            console.log('マウスin');
        });
    };
});
```

もう1つ, マウスが外に出た時のイベントも追加しましょう。

```main.js
var app = angular.module("myApp", []);

app.directive("enter", function () {
    return function (scope, element) {
        element.bind("mouseenter", function () {
            console.log('マウスin');
        });
    };
});

app.directive("leave", function () {
    return function (scope, element) {
        element.bind("mouseleave", function () {
            console.log('マウスout');
        });
    };
});
```

対応するdivにleave属性も追記します。

```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 enter leave>この上にマウスを乗せてみる。</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>
```

マウスをテキストの上に乗せてから外してみると,

![in and out](https://qiita-image-store.s3.amazonaws.com/0/20470/05934eac-3633-dc3b-cd29-fa653c33cd4e.png)

両方とも動いていることが分かりますね。

もうちょっと実用的な例として, マウスが上に乗ったらUIを変える処理にしてみましょう。

```main.js
var app = angular.module("myApp", []);

app.directive("enter", function () {
    return function (scope, element) {
        element.bind("mouseenter", function () {
            element.addClass("panel");
        });
    };
});

app.directive("leave", function () {
    return function (scope, element) {
        element.bind("mouseleave", function () {
            element.removeClass("panel");
        });
    };
});
```

addClass, removeClassはjQueryでも出てくるのでおやっと思う方も居ると思いますが, Angularの中にはjqLiteというjQueryのサブセットが含まれているので一部同じような関数が使えたりするのです。

panelクラスは読み込んでいるCSSフレームワークによってパネル風な装飾がなされます。マウスをテキストの上に乗せてみると, 

![実用的なDirective](https://qiita-image-store.s3.amazonaws.com/0/20470/1adab411-7a8c-c6f2-337f-522228d1a566.png)

リッチなパネル風の見た目になるのが分かります。

このままでも良いのですが, Angularではもう少しcleanな書き方が出来て, directiveの中でpanelクラスを直接書かずに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 enter="panel" leave>この上にマウスを乗せてみる。</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>
```

enter属性の値にpanelが入っているのに注目してください。こいつはjs側で以下のようにして取得できます。

```main.js
var app = angular.module("myApp", []);

app.directive("enter", function () {
    return function (scope, element, attrs) {
        element.bind("mouseenter", function () {
            element.addClass(attrs.enter);
        });
    };
});

app.directive("leave", function () {
    return function (scope, element, attrs) {
        element.bind("mouseleave", function () {
            element.removeClass(attrs.enter);
        });
    };
});
```

linkの引数にattrsが新しく追加されているのに注目です。これを指定してやることで, attrs.enterとすることでpanelが取得できるというわけです。

結果は上と同じですが, こちらの方がUIに関する値であるpanelをhtml側に含められるため統一感もあり, directive側も単体テストがしやすくなりキレイな書き方ができる！ということですね。

directiveについてはもう少しあるので, 次回に続きます。