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