web-dev-qa-db-ja.com

ディレクティブからコントローラースコープにアクセスする

作成中の<table>のソート列ヘッダーを表示する簡単なディレクティブを作成しました。

ngGrid.directive("sortColumn", function() {
    return {
        restrict: "E",
        replace: true,
        transclude: true,
        scope: {
            sortby: "@",
            onsort: "="
        },
        template: "<span><a href='#' ng-click='sort()' ng-transclude></a></span>",
        link: function(scope, element, attrs) {
            scope.sort = function () {

                // I want to call CONTROLLER.onSort here, but how do I access the controller scope?...
                scope.controllerOnSort(scope.sortby);
            };
        }
    };
});

いくつかのテーブルヘッダーが作成される例を次に示します。

<table id="mainGrid" ng-controller="GridCtrl>
<thead>
    <tr>
        <th><sort-column sortby="Name">Name</sort-column></th>
        <th><sort-column sortby="DateCreated">Date Created</sort-column></th>
        <th>Hi</th>
    </tr>
</thead>

したがって、ソート列をクリックすると、グリッドコントローラーでonControllerSort関数を実行したいのですが、行き詰まりました!これまでのところ、これを行うことができた唯一の方法は、各<sort-column>に対して、「onSort」の属性を追加し、ディレクティブでそれらを参照することです。

<sort-column onSort="controllerOnSort" sortby="Name">Name</sort-column>

しかし、私は常にcontrollerOnSortを呼び出したいので、それはあまり良いことではありません。そのため、すべてのディレクティブに組み込むことは少しbitいです。 HTMLで不要なマークアップを必要とせずに、ディレクティブ内でこれを行うにはどうすればよいですか?ディレクティブとコントローラーの両方が同じモジュール内で定義されている場合、それが役立ちます。

19
Matt Roberts

ラッパーとして2番目のディレクティブを作成します。

ngGrid.directive("columnwrapper", function() {
  return {
    restrict: "E",
    scope: {
      onsort: '='
    }
  };
});

次に、外部ディレクティブで1回呼び出す関数を参照するだけです。

<columnwrapper onsort="controllerOnSort">
  <sort-column sortby="Name">Name</sort-column>
  <sort-column sortby="DateCreated">Date Created</sort-column>
</columnwrapper>

「sortColumn」ディレクティブでは、次に呼び出すことにより、その参照された関数を呼び出すことができます

scope.$parent.onsort();

実際の例については、このフィドルを参照してください: http://jsfiddle.net/wZrjQ/1/

もちろん、ハードコードされた依存関係を気にしない場合は、1つのディレクティブにとどまり、親スコープ(それが問題のコントローラーになる)で関数を呼び出すこともできます。

scope.$parent.controllerOnSort():

これを示す別のフィドルがあります: http://jsfiddle.net/wZrjQ/2

この解決策は、他の回答( https://stackoverflow.com/a/19385937/2572897 )の解決策と同じ効果(ハードカップリングに関する同じ批判)を持ちますが、少なくともそのソリューションよりもいくらか簡単です。とにかくあなたが一生懸命なら、おそらく$ scope。$ parentでいつでも利用できるので、コントローラーを参照することに意味はないと思います(ただし、スコープを設定する他の要素に注意してください)。

しかし、私は最初の解決策に行きます。少しマークアップを追加しますが、問題を解決し、きれいな分離を維持します。また、2番目のディレクティブを直接ラッパーとして使用する場合、$ scope。$ parentがアウターディレクティブと一致することを確認できます。

24
Juliane Holzt

&local scopeプロパティを使用すると、ディレクティブのコンシューマーは、ディレクティブが呼び出すことができる関数を渡すことができます。

Illustration of & scope property

詳細を参照してください こちら

同様の質問への回答 は、ディレクティブコードからコールバック関数に引数を渡す方法を示しています。

20
gm2008

ディレクティブでngControllerを必要とし、リンク関数を次のように変更します。

ngGrid.directive("sortColumn", function() {
    return {
        ...
        require: "ngController",
        ...
        link: function(scope, element, attrs, ngCtrl) {
            ...
        }
    };
});

ngCtrlとして得られるのは、コントローラーGridCtrlです。ただし、そのスコープは取得できません。次の行で何かをする必要があります。

xxxx.controller("GridCtrl", function($scope, ...) {
    // add stuff to scope as usual
    $scope.xxxx = yyyy;

    // Define controller public API
    // NOTE: USING this NOT $scope
    this.controllerOnSort = function(...) { ... };
});

リンク関数から次のように単純に呼び出します。

ngCtrl.controllerOnSort(...);

このrequireは最初の親ngControllerを取得することに注意してください。 GridCtrlとディレクティブの間に別のコントローラーが指定されている場合、そのコントローラーを取得します。

原理を実証するフィドル(メソッドを使用して親ng-controllerにアクセスするディレクティブ): http://jsfiddle.net/NAfm5/1/


このソリューションは、望ましくない密結合を引き起こす可能性があると人々は恐れています。これが実際に懸念事項である場合は、次のように対処できます。

コントローラーと並行するディレクティブを作成し、masterと呼びます。

<table id="mainGrid" ng-controller="GridCtrl" master="controllerOnSort()">

このディレクティブは、コントローラーの目的のメソッドを参照します(したがって、分離)。

子ディレクティブ(あなたの場合はsort-column)にはmasterディレクティブが必要です:

require: "^master"

$parseサービスを使用すると、指定したメソッドをマスターコントローラーのメンバーメソッドから呼び出すことができます。この原則を実装する更新されたフィドルを参照してください: http://jsfiddle.net/NAfm5/3/

これを行う別の方法がありますが、経験が比較的不足しているため、このようなソリューションの適合性について話すことはできません。とにかく情報提供のみを目的としてそれを伝えます。

列で、スコープ変数属性を作成します。

<sort-column data-sortby="sortby">Date Created</sort-column>

次に、コントローラーでスコープ変数を定義します。

$scope.sortby = 'DateCreated' // just a default sort here

次に、コントローラーにソート関数を追加します。

$scope.onSort = function(val) {
    $scope.sortby = val;
}

次に、マークアップでng-clickを接続します。

<sort-column data-sortby="sortby" ng-click="onSort('DateCreated')">Date Created</sort-column>

次に、ディレクティブでsortby属性をディレクティブスコープに追加します。

scope: {
    sortby: '=' // not sure if you need
}

また、「link:」関数に$ watchを追加します。

scope.$watch('sortby', function () {
    ... your sort logic here ...
}

このアプローチIMOの利点は、ディレクティブが完全に分離されていることです。実行パスのその部分でコントローラーにonSortを実際に残さないため、ディレクティブからonSortにコールバックする必要はありません。

ソートが完了するまで待機するようにコントローラーに指示する必要がある場合、コントローラーでイベントを定義できます。

$scope.$on("_sortFinished", function(event, message){
   ..do something...  
});

次に、ディレクティブでイベントを発行するだけで、プロセスが完了します。

$scope.$emit('_sortFinished');

他の方法もありますが、この種の方法では、コントローラーがリッスンする必要があるため、タイトなカップリングが追加されます。そして、あなたの指示は特定の偶数を放出する必要があります...しかし、それらはとにかく密接に関連しているので、それはあなたにとって問題ではないかもしれません。

2
Brandon

クレイジーと呼びますが、requireをいじるのではなく、そのための組み込みメソッドを介して要素からコントローラーを取得する方が簡単です。

var mod = angular.module('something', []).directive('myDir', 
  function () {
    return {
      link: function (scope, element) {
        console.log(element.controller('myDir'));
      },
      controller: function () {
        this.works = function () {};
      },
      scope: {}
    }
  }
);

http://plnkr.co/edit/gY4rP0?p=preview

1
marksyzm