Angular.js入門 (5)ディレクティブ その2


前回: [Angular.js入門 (4)ディレクティブ その1](http://qiita.com/_rdtr/items/5edffeb96a9663fa1b1c)

## DirectiveとControllerの連携
ディレクティブとコントローラとで値を共有したいときはどうすれば良いのでしょう？
結論から言うとスコープを通じて渡してやれば良いです。

具体的には

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

app.controller("AppCtrl", function ($scope) {
    $scope.alertTest = function() {
        alert("テストだよ！");
    };
});
```

こんなコントローラの, alertTest関数をdirective内で使いたいとしましょう。コントローラAppCtrlのスコープ配下ではalertTestという関数が生きていることになるので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="AppCtrl">
      <div enter="alertTest()">この上にマウスを乗せてみる。</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>
```

enter属性の値として, 関数名()を渡していることに注目してください。
あとは

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

app.controller("AppCtrl", function ($scope) {
    $scope.alertTest = function() {
        alert("テストだよ！");
    };
});

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

と, enterディレクティブを追加します。$applyというメソッドを用いてattrs.enter = "alertTest()"関数を適用していることになります。()をつけているので関数は即実行され, 

![alertTest()の実行](https://qiita-image-store.s3.amazonaws.com/0/20470/47caa0c6-e24d-7c53-bbdf-27a2754cae11.png)

テキストの上にマウスを乗せるとアラートが上がります。

ちなみにこのapplyメソッド, Angular自体が裏でバリバリに使いまくっているものなのですが, ユーザが自分で使うのはAngular由来ではない関数を最初のページ読み込み時以降に呼び出すケースだけというのは覚えておいてもいいかもしれないです。

## Directive間の連携

今度はディレクティブ同士の間での連携です。どうやってやるのかというと, 連携したい先のディレクティブで連携元のディレクティブをrequireします。具体例です。

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

app.directive("test1", function () {
    return {
       controller: function ($scope) {
            $scope.test_array = [];
            this.addHoge = function () {
                $scope.test_array.push("hoge");
            };
            this.addFuga = function () {
                $scope.test_array.push("fuga");
            };
        },
        link: function (scope, element) {
            element.bind("mouseenter", function(){
                console.log(scope.test_array);
            });
        }
    };
});
```

まずは上のようなtest1ディレクティブを用意します。ここでダミー配列と, 配列に要素を追加する関数を2つ(addHoge, addFuga)用意しています。これだけだとまだ何も起きないので, これらの関数を使うもう１個ディレクティブを追加しましょう。

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

app.directive("test1", function () {
    return {
        controller: function ($scope) {
            $scope.test_array = [];
            this.addHoge = function () {
                $scope.test_array.push("hoge");
            };
            this.addFuga = function () {
                $scope.test_array.push("fuga");
            };
        },
        link: function (scope, element) {
            element.bind("mouseenter", function(){
                console.log(scope.test_array);
            });
        }
    };
});

app.directive("test2", function () {
    return {
        require: "test1",
        link: function (scope, element, attrs, test1Ctrl) {
            test1Ctrl.addHoge();
        }
    };
})
```

test2ディレクティブでtest1ディレクティブをrequireしています。そのあとlink関数にtest1Ctrlという引数が追加されています。ここにtest1のcontrollerがパラメータとして引き渡されるというわけです。

![ディレクティブ同士の連携](https://qiita-image-store.s3.amazonaws.com/0/20470/1cb9fa62-6910-c885-cab1-752854a983c4.png)

マウスを乗せるとhogeが追加されたtest_arrayがConsoleに出力されます。

もう少し見てみましょう。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 test1 test2 class="button">この上にマウスを乗せてみる。</div>
   <div test1 test3 class="button">この上にマウスを乗せてみる。</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>
```

FoundationのボタンClassの装飾があたるので

![ボタン化](https://qiita-image-store.s3.amazonaws.com/0/20470/cd27bf8b-d3d6-c23a-f9c0-1ab2e916b497.png)
こんな見た目になるかと思います。

ここでjsを

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

app.directive("test1", function () {
    return {
        controller: function ($scope) {
            $scope.test_array = [];
            this.addHoge = function () {
                $scope.test_array.push("hoge");
            };
            this.addFuga = function () {
                $scope.test_array.push("fuga");
            };
        },
        link: function (scope, element) {
            element.bind("mouseenter", function(){
                console.log(scope.test_array);
            });
        }
    };
});

app.directive("test2", function () {
    return {
        require: "test1",
        link: function (scope, element, attrs, test1Ctrl) {
            test1Ctrl.addHoge();
        }
    };
})

app.directive("test3", function () {
    return {
        require: "test1",
        link: function (scope, element, attrs, test1Ctrl) {
            test1Ctrl.addFuga();
        }
    };
})
```

とします。新しいtest3ディレクティブを作り, こちらにmouseenterしたら"fuga"を出力したいわけですね。
実際にやってみましょう。

![fuga!!](https://qiita-image-store.s3.amazonaws.com/0/20470/6209c8c8-66de-9af1-fcd1-8d5dacd925c7.png)

左のボタンにマウスを乗せたらhogeが出てほしいのに, 両方ともfugaになってしまいました。これはAngularのscopeが, デフォルトで「同じ要素で複数のディレクティブが新しいスコープをつくろうとすると, 1つしか作らない」という動きになっているためです。

javascriptのprototypeの話を思い出してください。

```javascript
function Dog() {}
Dog.prototype.bark = function() {
  console.log('わんわん');
};

var dog = new Dog();
dog.bark(); //'わんわん';
```

dogオブジェクトそのものにはbarkメソッドはありませんが, Dogを継承しているのでそのプロトタイプを遡ってbarkメソッドを参照し, 実行するのでした。逆にdogでだけbark関数の動きを変えたければ

```javascript
dog.bark = function () {console.log("ばうばう");}
dog.bark(); //'ばうばう';
```

としてやればdogオブジェクト内にbarkがあるので遡って探しにいかないわけです。

```javascript
dog.bark = function () {console.log("ばうばう");}
dog.bark = function () {console.log("きゃんきゃん");}
dog.bark();
```

ではこれはどうなるでしょう？同じbarkを上掛しているので「きゃんきゃん」に決まっていますね。ここでいう「同じbark」というのは, 同じスコープ内にあるbarkという変数ということです。

Angularのスコープも, javascriptのオブジェクトに変わりはありません。コントローラを作ると新しいスコープがそのコントローラ内に作られます。ディレクティブについても同様です。今、test2により新しいスコープが作成されたのですが, test3がそれを上書いてしまったので, test2のディレクティブでもtest3と同じ動きになってしまったのです。

## Isolated scope
まだイメージレベルでしか理解できないと思いますが, ここではとりあえずどうすれば良いのかを先に書いておきます。scopeはオブジェクトで一般のプロトタイプ継承に従うと言いましたが, Angularではこのルールを破ってプロトタイプのリンクをぶったぎり, 独自のスコープ(Isolated scope)を作ることができます。

```js:main.js
app.directive("test1", function () {
    return {
        scope: {},
        controller: function ($scope) {
            $scope.test_array = [];
            this.addHoge = function () {
                $scope.test_array.push("hoge");
            };
            this.addFuga = function () {
                $scope.test_array.push("fuga");
            };
        },
        link: function (scope, element) {
            element.bind("mouseenter", function(){
                console.log(scope.test_array);
            });
        }
    };
});

app.directive("test2", function () {
    return {
        require: "test1",
        link: function (scope, element, attrs, test1Ctrl) {
            test1Ctrl.addHoge();
        }
    };
})

app.directive("test3", function () {
    return {
        require: "test1",
        link: function (scope, element, attrs, test1Ctrl) {
            test1Ctrl.addFuga();
        }
    };
})
```

たった1行。`scope: {}`と追記することで, 子スコープはisolatedになることを指定できるのです。

![hoge&fuga](https://qiita-image-store.s3.amazonaws.com/0/20470/e8fc2649-9594-3ee8-5e3e-7a7307477f7c.png)

きちんとお互いの関数が出力されるようになりました。

次回以降はスコープについてもうちょっと詳しく見て行きたいと思います。