web-dev-qa-db-ja.com

AngularJSでディレクティブとコントローラー間のスコープを適切にバインドする方法

私はanugularJSでnレベルの階層的な順序なしリストを生成しようとしていますが、成功させることができました。しかし、今では、ディレクティブとコントローラーの間にスコープの問題があります。ディレクティブテンプレートのng-clickで呼び出された関数内から、親のスコーププロパティを変更する必要があります。

http://jsfiddle.net/ahonaker/ADukg/2046/ を参照してください-これがJSです

var app = angular.module('myApp', []);

//myApp.directive('myDirective', function() {});
//myApp.factory('myService', function() {});

function MyCtrl($scope) {
    $scope.itemselected = "None";
    $scope.organizations = {
        "_id": "SEC Power Generation",
        "Entity": "OPUNITS",
        "EntityIDAttribute": "OPUNIT_SEQ_ID",
        "EntityID": 2,
        "descendants": ["Eastern Conf Business Unit", "Western Conf Business Unit", "Atlanta", "Sewanee"],
        children: [{
            "_id": "Eastern Conf Business Unit",
            "Entity": "",
            "EntityIDAttribute": "",
            "EntityID": null,
            "parent": "SEC Power Generation",
            "descendants": ["Lexington", "Columbia", "Knoxville", "Nashville"],
            children: [{
                "_id": "Lexington",
                "Entity": "OPUNITS",
                "EntityIDAttribute": "OPUNIT_SEQ_ID",
                "EntityID": 10,
                "parent": "Eastern Conf Business Unit"
            }, {
                "_id": "Columbia",
                "Entity": "OPUNITS",
                "EntityIDAttribute": "OPUNIT_SEQ_ID",
                "EntityID": 12,
                "parent": "Eastern Conf Business Unit"
            }, {
                "_id": "Knoxville",
                "Entity": "OPUNITS",
                "EntityIDAttribute": "OPUNIT_SEQ_ID",
                "EntityID": 14,
                "parent": "Eastern Conf Business Unit"
            }, {
                "_id": "Nashville",
                "Entity": "OPUNITS",
                "EntityIDAttribute": "OPUNIT_SEQ_ID",
                "EntityID": 4,
                "parent": "Eastern Conf Business Unit"
            }]
        }]
    };

    $scope.itemSelect = function (ID) {
        $scope.itemselected = ID;
    }
}

app.directive('navtree', function () {
    return {
        template: '<ul><navtree-node ng-repeat="item in items" item="item" itemselected="itemselected"></navtree-node></ul>',
        restrict: 'E',
        replace: true,
        scope: {
            items: '='
        }
    };
});

app.directive('navtreeNode', function ($compile) {
    return {
        restrict: 'E',
        template: '<li><a ng-click="itemSelect(item._id)">{{item._id}} - {{itemselected}}</a></li>',
        scope: {
            item: "=",
            itemselected: '='
        },
        controller: 'MyCtrl',
        link: function (scope, Elm, attrs) {
            if ((angular.isDefined(scope.item.children)) && (scope.item.children.length > 0)) {
                var children = $compile('<navtree items="item.children"></navtree>')(scope);
                Elm.append(children);
            }
        }
    };
});

そして、これがHTMLです

<div ng-controller="MyCtrl">
    Selected: {{itemselected}}

    <navtree items="organizations.children"></navtree>
</div>

リストはモデルから生成されることに注意してください。 ng-clickは関数を呼び出して親スコーププロパティ(itemselected)を設定しますが、変更はローカルでのみ発生します。アイテムをクリックすると、「選択済み:なし」が「選択済み:xxx」に変わるはずです。xxxはクリックされたアイテムです。

親スコープとディレクティブの間でプロパティを適切にバインドしていませんか?プロパティの変更を親スコープに渡すにはどうすればよいですか?

これが明確であることを願っています。

助けてくれてありがとう。

18
user2165994

この作業フィドルをご覧ください http://jsfiddle.net/eeuSv/

私がやったのは、_navtree-node_ディレクティブ内で親コントローラーを要求し、そのコントローラーで定義されたメンバー関数を呼び出すことでした。メンバー関数はsetSelectedです。 _this.setSelected_ではなく_$scope.setSelected_であることに注意してください。次に、_navtree-node_スコープメソッドitemSelectを定義します。アンカータグをクリックすると、_navtree-node_スコープでitemSelectメソッドが呼び出されます。この順番は、選択されたIDを渡すコントローラーメンバーメソッドsetSelectedを呼び出します。

scope.itemSelect = function(id){ myGreatParentControler.setSelected(id) }

18

Maxdecが正しい、これはスコーピングに関係しています。残念ながら、これはAngularJSのドキュメントが初心者(私のようなもの)にとって誤解を招くほど複雑なケースです。

警告:私がこれを説明しようとするとき、私は少し長引くことに気をつけてください。コードを表示するだけの場合は、この JSFiddle にアクセスしてください。 egghead.io ビデオは、AngularJSについて学ぶのに非常に貴重であることがわかりました。

問題の私の理解は次のとおりです。ディレクティブ(navtree、navitem)の階層があり、navitemから「ツリーの上の」情報をルートコントローラーに渡したい場合。 AngularJSは、よく書かれた一般的なJavascriptと同様に、変数のスコープについて厳密に設定されているため、ページ上で実行されている他のスクリプトを誤って台無しにすることはありません。

Angular=には特別な構文(&)があり、これにより分離スコープを作成し、親スコープで関数を呼び出すことができます。

// in your directive
scope: {
   parentFunc: '&'
}

ここまでは順調ですね。基本的に次のことを実行するため、複数レベルのディレクティブがある場合は注意が必要です。

  1. 変数を受け入れてモデルを更新する関数をルートコントローラーに持つ
  2. 中レベルのディレクティブ
  3. ルートコントローラーと通信できる子レベルのディレクティブ

問題は、子レベルのディレクティブがルートコントローラーを認識できないことです。私の理解では、次のように動作するディレクティブ構造に「チェーン」を設定する必要があるということです。

最初:ルートコントローラーに、ルートビューコントローラーのスコープへの参照を持つ関数を返す関数があります:

$scope.selectFunctionRoot = function () {
    return function (ID) {
        $scope.itemselected = ID;
    }
}

Second:中間レベルのディレクティブを設定して、次のようなものを返す独自の選択関数(子に渡す関数)を設定します。このコードが実際に実行されると、子レベルのディレクティブのコンテキスト内にあるため、中レベルのディレクティブのスコープをどのように保存する必要があるかに注意してください。

// in the link function of the mid-level directive. the 'navtreelist'
scope.selectFunctionMid = function () {
    // if we don't capture our mid-level scope, then when we call the function in the navtreeNode it won't be able to find the mid-level-scope's functions            
    _scope = scope;
    return function (item_id) {
        console.log('mid');
        console.log(item_id);

        // this will be the "root" select function
        parentSelectFunction = _scope.selectFunction();
        parentSelectFunction(item_id);
    };
};

3番目:子レベルのディレクティブ(navtreeNode)で、ローカル関数を呼び出すng-clickに関数をバインドします。次に、ルートコントローラーまで「チェーンを呼び出し」ます。

// in 'navtreeNode' link function
scope.childSelect = function (item_id) {
    console.log('child');
    console.log(item_id);

    // this will be the "mid" select function  
    parentSelectFunction = scope.selectFunction();
    parentSelectFunction(item_id);
};

これが更新された JSFiddleのフォーク で、コードにコメントがあります。

11
Benmj

各ディレクティブが独自のスコープを作成するためである可能性があります(実際には、そうするように指示します)。
ディレクティブに関する詳細はこちらをご覧ください こちら 、特に「ディレクティブの作成(長いバージョン)」の章。

scope-以下に設定されている場合:

true-このディレクティブに対して新しいスコープが作成されます。同じ要素の複数のディレクティブが新しい​​スコープを要求する場合、1つの新しいスコープのみが作成されます。テンプレートのルートは常に新しいスコープを取得するため、新しいスコープルールはテンプレートのルートには適用されません。

{}(オブジェクトハッシュ)-新しい「分離」スコープが作成されます。 「分離」スコープは、親スコープからプロトタイプ的に継承しないという点で通常のスコープとは異なります。これは、親スコープ内のデータを誤って読み取ったり変更したりしない再利用可能なコンポーネントを作成するときに役立ちます。

各ディレクティブには独自の「分離」スコープがあるため、変更はMyCtrlスコープに反映されません。

そのため、クリックしてもローカル$scope.itemselected変数であり、それらのすべてではありません。

3
maxdec