Подтвердить что ты не робот

Область управления доступом от директивы

Я создал простую директиву, которая отображает заголовки столбцов сортировки для <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, поэтому его включение в каждую директиву немного уродливо. Как это сделать в директиве, не требуя ненужной разметки в моем HTML? Как директива, так и контроллер определяются в одном модуле, если это помогает.

4b9b3361

Ответ 1

Создайте вторую директиву в качестве обертки:

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

Затем вы можете просто ссылаться на функцию для вызова один раз во внешней директиве:

<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/

Конечно, если вам не нужны жестко закодированные зависимости, вы также можете остаться с одной директивой и просто вызвать функцию в родительской области (которая будет тогда управляться контроллером) через

scope.$parent.controllerOnSort():

У меня есть еще одна скрипка, показывающая это: http://jsfiddle.net/wZrjQ/2

Это решение будет иметь тот же эффект (с той же критикой в ​​отношении жесткой связи), что и решение в другом ответе (fooobar.com/questions/276233/...), но по крайней мере, несколько легче, чем это решение. В любом случае, если вы все равно работаете со связью, я не думаю, что есть точка в ссылке на контроллер, поскольку он, скорее всего, будет доступен в $scope. $Parent все время (но остерегайтесь других элементов, настраивающих область).

Я бы пошел на первое решение. Он добавляет небольшую разметку, но решает проблему и поддерживает чистое разделение. Также вы можете быть уверены, что $scope. $Parent соответствует внешней директиве, если вы используете вторую директиву как прямую оболочку.

Ответ 2

Свойство "Локальная область" позволяет потребителю директивы проходить в функции, которую может вызывать директива.

Illustration of & scope property

Подробнее здесь.

Вот ответ на аналогичный вопрос, в котором показано, как передать аргумент в функции обратного вызова из кода директивы.

Ответ 3

В вашей директиве требуется 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(...);

Обратите внимание, что для этого требуется получить родительский ngController. Если есть другой контроллер, указанный между GridCtrl и директивой, вы получите тот.

Скрипка, демонстрирующая принцип (директива, обращающаяся к родительскому ng-controller с методами): http://jsfiddle.net/NAfm5/1/


Люди опасаются, что это решение может ввести нежелательную жесткую связь. Если это действительно вызывает беспокойство, ее можно решить следующим образом:

Создайте директиву, которая будет бок о бок с контроллером, позволяет называть ее master:

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

Эта директива ссылается на желаемый метод контроллера (таким образом: развязка).

Директива child (sort-column в вашем случае) требует директивы master:

require: "^master"

Используя службу $parse, указанный метод может быть вызван из метода-члена главного контроллера. См. Обновленный скрипт, реализующий этот принцип: http://jsfiddle.net/NAfm5/3/

Ответ 4

Есть и другой способ сделать это, хотя, учитывая мой относительный недостаток опыта, я не могу говорить о пригодности такого решения. Я передам его в любом случае только для информационных целей.

В столбце вы создаете атрибут переменной области видимости:

<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');

Существуют и другие способы сделать это, и этот тип добавляет некоторую жесткую связь, потому что ваш контроллер должен слушать. и ваша директива должна испускать конкретную четность... но это может не быть проблемой для вас, поскольку они тесно связаны друг с другом.

Ответ 5

Назовите меня сумасшедшим, но проще просто получить контроллер от элемента с помощью встроенного метода для этого, вместо того, чтобы возиться с 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