Help us understand the problem. What is going on with this article?

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

More than 5 years have passed since last update.

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

DirectiveとControllerの連携

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

具体的には

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属性の値として, 関数名()を渡していることに注目してください。
あとは

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()の実行

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

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

Directive間の連携

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

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)用意しています。これだけだとまだ何も起きないので, これらの関数を使うもう1個ディレクティブを追加しましょう。

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がパラメータとして引き渡されるというわけです。

ディレクティブ同士の連携

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

もう少し見てみましょう。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の装飾があたるので

ボタン化
こんな見た目になるかと思います。

ここで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!!

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

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

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

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

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

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

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

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)を作ることができます。

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

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

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

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away