こんにちは。imaiです。待望のAngularJS1.4がリリースされましたね。Directive関係では、bindToController
の機能が向上しました。
このbindToController
とは一体何なのか。使って嬉しいことは何か。どう機能が向上したのか。について書いていきます。また、最後に自作Directiveを作っていく際に筆者がよくつまずく点を記しておきます。
この記事の対象者:AngularJSに触れたことがある、自作のDirectiveを作ったことがある
#bindToControllerとは
AngularJS公式サイトのReferenceからbindToConroller
の説明を意訳すると、
再利用可能なコンポーネントを作るために、分離?隔離?スコープや
controllerAs
を用いる際に、bindToController:true
とすることでコンポーネントのプロパティはcontroller
に結び付けられる(scope
には結びつかない)。コントローラが生成していれば、分離スコープに結びついた値を利用することができる。
bindToController
は、コントローラーと分離スコープ(他のスコープから切り離されたDirectiveが持てるスコープ)を結びつけると解釈していいと思います。
"bind to controller" === "bind an isolated scope to a controller"
ということ。
ざっくりいいますと、これまでDirectiveを自作する時に、コントローラーへ値を引き渡し、バインディングするには$scope
が必要でしたが、これが$scope
いらずで行えるようになりました。ng-controllerのas構文では先行して可能だったことがDirectiveでも可能になったんです。また、1.4では、どの値をバインディングするのか、どの値をバインディングしないのかを明確に定めることができるようになりました。
#bindToControllerを使って嬉しいこと、機能向上したところ
では実際に「bindToController
を利用することで嬉しいことは何か」について書いていきます。例を示した方が早いと思うので、
- AngularJS 1.2.xを用いて自作Directiveを作る場合
- AngularJS 1.3.xを用いて自作Directiveを作る場合
- AngularJS 1.4.xを用いて自作Directiveを作る場合
この3つの例を見ていきます。お題は「ボタンがクリックされたら、クリックされた回数を表示する」
##AngularJS 1.2.xの場合
###ポイント
AngularJS 1.2.x系では、Directive間にて値のやり取りをするには$scope
を用いるのが一番簡単のように思えます。しかしながらcontrollerAs
を用いた方がDirectiveがネストされた時の煩雑さを防げます(参考)。よってcontrollerAs
を使いたいのですが、コントローラにおいて変更を感知するには$scope.$watch
を用いるしかないため、書き方が複雑になってしまいます。
###ソース
<div ng-app="myApp">
<parent-directive>
</parent-directive>
</div>
angular.module("myApp",[]);
//---親のDirective
//------親のDirectiveのcontroller
function parentController(){
this.num = 0;
this.increment = function(){
this.num++;
}
}
//------親のDirectiveのDirective Definition Object
function parentDirective(){
var ddo = {
restrict: "E",
scope: {},
controller: parentController,
controllerAs: "parent",
template: [
"<div style='background-color: #f0f0f0'>",
"<span>num: {{parent.num}} in parent </span>",
"<button ng-click='parent.increment()'>+1</button>",
"<child-directive prop1='parent.num'></child-directive>",
"</div>"].join("")
};
return ddo;
}
angular.module("myApp").directive("parentDirective", parentDirective);
//---子のDirective
//------子のDirectiveのcontroller
function childController($scope){
//ここでのthisは、templateの{{child}}。(controllerAs)
//numのをwatchしなければ変更をthisに伝えることはできない。
$scope.$watch("num", function(newVal){
this.num = newVal;
}.bind(this));
}
//------子のDirectiveのDirective Definition Object
function childDirective(){
var ddo = {
restrict: "E",
scope: {
num: "=prop1"
},
controller: childController,
controllerAs: "child",
template: [
"<div style='background-color: #d0d0d0'>",
"<span>num: {{child.num}} in child </span>",
"</div>"].join("")
};
return ddo;
}
angular.module("myApp").directive("childDirective", childDirective);
##AngularJS 1.3.xの場合
###ポイント
AngularJS 1.3.xではbindToController
を利用できます。bindToController: true
とすることによりcontrollerAs
を用いてコントローラ内でthis
を通じて値を参照できます。$scope.$watch
を用いずとも、親の変更を反映することできます。childControllerを見れば楽に書けるようになったことは明らかです。
###ソース
angular.module("myApp",[]);
//---親のDirective
//------親のDirectiveのcontroller
function parentController(){
this.num = 0;
this.increment = function(){
this.num++;
}
}
//------親のDirectiveのDirective Definition Object
function parentDirective(){
var ddo = {
restrict: "E",
scope: {},
controller: parentController,
controllerAs: "parent",
template: [
"<div style='background-color: #f0f0f0'>",
"<span>num: {{parent.num}} in parent </span>",
"<button ng-click='parent.increment()'>+1</button>",
"<child-directive prop1='parent.num'></child-directive>",
"</div>"].join("")
};
return ddo;
}
angular.module("myApp").directive("parentDirective", parentDirective);
//---子のDirective
//------子のDirectiveのcontroller
function childController(){
//ここでのthisは、templateの{{child}}。(controllerAs)
//bindToControllerによって、scopeがthis.~に紐づく。
console.log(this.num);
}
//------子のDirectiveのDirective Definition Object
function childDirective(){
var ddo = {
restrict: "E",
scope: {
num: "=prop1"
},
controller: childController,
controllerAs: "child",
//bindToCotrollerをtrueにすることでthisとscopeを紐づけることができる。
bindToController: true,
template: [
"<div style='background-color: #d0d0d0'>",
"<span>num: {{child.num}} in child </span>",
"</div>"].join("")
};
return ddo;
}
angular.module("myApp").directive("childDirective", childDirective);
##AngularJS 1.4.xの場合
###ポイント
AngularJS 1.4.xでは、bindToController
にて紐づける値を直接指定することができます。bindToController:{hoge: "="}
と書いていきます。スコープオプションは書かなくてもいい。しかしながら、scope:{}
もしくはscope:true
と書いておかないと動きません。ので、分離スコープを生成するscope:{}
を指定すればよいでしょう。
###ソース
angular.module("myApp",[]);
//---親のDirective
//------親のDirectiveのcontroller
function parentController(){
this.num = 0;
this.increment = function(){
this.num++;
}
//this.notBindはbindToControllerにてbindしない場合。
this.notBind = "notBind";
}
//------親のDirectiveのDirective Definition Object
function parentDirective(){
var ddo = {
restrict: "E",
scope: {},
controller: parentController,
controllerAs: "parent",
template: [
"<div style='background-color: #f0f0f0'>",
"<span>num: {{parent.num}} in parent </span>",
"<button ng-click='parent.increment()'>+1</button>",
"<child-directive prop1='parent.num' prop2='parent.notBind'></child-directive>",
"</div>"
].join("")
};
return ddo;
}
angular.module("myApp").directive("parentDirective", parentDirective);
//---子のDirective
//------子のDirectiveのcontroller
function childController(){
//ここでのthisは、templateの{{child}}。(controllerAs)
//bindToControllerによって、scopeがthis.~に紐づく。
console.log(this.num);
//this.notBindは紐づきません。
console.log(this.notBind);
}
//------子のDirectiveのDirective Definition Object
function childDirective(){
var ddo = {
restrict: "E",
scope: {
notBind: "=prop2"
},
controller: childController,
controllerAs: "child",
//1.4からはbindToCotrollerにて直接bindする値を選択できる。
bindToController: {
num: "=prop1"
},
template: [
"<div style='background-color: #d0d0d0'>",
"<span>num: {{child.num}} in child </span>",
"</div>"
].join("")
};
return ddo;
}
angular.module("myApp").directive("childDirective", childDirective);
#注意点
自作Directiveを作ろうとした時に筆者がよくつまずく点を記しておきます。
##2語以上のDirective名だと動かない
angular.module().directive("sampleDirective", sampleDirective);
とした時のHTML要素は
<sampleDirective></sampleDirective>
ではなく
<sample-directive></sample-directive>
##2語以上のscope名だと動かない
{child: "=sampleModel"}
のとき
<sample-directive sampleModel='data'></sample-directive>
ではなく
<sample-directive sample-model='data'></sample-directive>
Directive名はキャメルケース、テンプレート上での名前はハイフンケース
##bindToControllerがうまく動かない
bindToController: {hoge: "=foo"}
としたけど動かない。これはかなりひっかけではないか。
上でも書いたが、scope:{}
もしくはscope:true
と書いておかないと動きません。
#最後に
1.4ではDirectiveの扱いが簡単に、そして分かりやすく、便利になったと思います。
今回の話題を、Angular Meetup Kyoto #1 @京都6.19 presented by ng-kyotoにてもう少し詳しく話します。お時間がありましたらお越しください。