LoginSignup
63
63

More than 5 years have passed since last update.

AngularJS1.4とbindToController

Posted at

こんにちは。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>
parent.js
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);
child.js
//---子の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を見れば楽に書けるようになったことは明らかです。

ソース

parent.js
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);
child.js
//---子の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:{}を指定すればよいでしょう。

ソース

parent.js
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);
child.js
//---子の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にてもう少し詳しく話します。お時間がありましたらお越しください。

63
63
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
63
63