前回: Angular.js入門 (4)ディレクティブ その1
DirectiveとControllerの連携
ディレクティブとコントローラとで値を共有したいときはどうすれば良いのでしょう?
結論から言うとスコープを通じて渡してやれば良いです。
具体的には
var app = angular.module("myApp", []);
app.controller("AppCtrl", function ($scope) {
$scope.alertTest = function() {
alert("テストだよ!");
};
});
こんなコントローラの, alertTest関数をdirective内で使いたいとしましょう。コントローラAppCtrlのスコープ配下ではalertTestという関数が生きていることになるので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属性の値として, 関数名()を渡していることに注目してください。
あとは
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()"関数を適用していることになります。()をつけているので関数は即実行され,
テキストの上にマウスを乗せるとアラートが上がります。
ちなみにこのapplyメソッド, Angular自体が裏でバリバリに使いまくっているものなのですが, ユーザが自分で使うのはAngular由来ではない関数を最初のページ読み込み時以降に呼び出すケースだけというのは覚えておいてもいいかもしれないです。
Directive間の連携
今度はディレクティブ同士の間での連携です。どうやってやるのかというと, 連携したい先のディレクティブで連携元のディレクティブをrequireします。具体例です。
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個ディレクティブを追加しましょう。
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を以下のように変えます。
<!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を
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"を出力したいわけですね。
実際にやってみましょう。
左のボタンにマウスを乗せたら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)を作ることができます。
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になることを指定できるのです。
きちんとお互いの関数が出力されるようになりました。
次回以降はスコープについてもうちょっと詳しく見て行きたいと思います。