Пользовательская директива Angular, передающая событие, но не вложенным детям?
Я создал аккордеонную директиву на Angular, которая может быть вложенной, чтобы в аккордеоне могли быть дети аккордеоны.
Я хочу транслировать событие, когда аккордеон открывается и закрывается, чтобы другие директивы могли его прослушивать (например, меню внутри панели аккордеона может раскрыться, когда аккордеон внутри него открыт).
Дело в том, что если во внешнем есть вложенный внутренний аккордеон, я не хочу, чтобы событие транслировалось дочерним элементам внутреннего аккордеона, потому что внутренний аккордеон не транслировал событие открытия / закрытия.
На всякий случай, если это не имеет смысла, иначе говоря, элемент внутри вложенного аккордеона должен иметь возможность прослушивать событие открытия / закрытия, транслируемое аккордеоном, в котором он находится, а не тем, которое находится дальше по дереву DOM.
Надеюсь, есть простое решение для этого.
Обновление: добавлено демо здесь: http://jsfiddle.net/jonhobbs/xr1kLqba/
Я явно не понял $broadcast и $on должным образом, поскольку демонстрационные события в настоящее время вообще не работают, но должно быть ясно, что я пытаюсь сделать!
Изменить: Очевидно, что все ссылки на jsfiddle должны сопровождаться кодом, так что вот некоторые.
var app = angular.module('app', []);
app.controller('MainCtrl', function($scope) {
$scope.mainMenuOpen = true;
})
.directive('myPanel', function() {
return {
restrict: 'EA',
scope: {
isOpen: '=?'
},
link: function($scope, $element, $attrs) {
$element.find('> BUTTON').bind('click', function() {
$scope.isOpen = !$scope.isOpen;
if ($scope.isOpen) {
$scope.$broadcast('panelOpened');
}
});
}
};
})
.directive('reactToPanelOpenClose', function() {
return {
restrict: 'EA',
scope: {},
link: function($scope, $element, $attrs) {
$scope.$on('panelOpened', function() {
alert("clicked");
$element.css({ 'background-color': 'red' });
});
}
};
});
3 ответа
Безусловно, самый простой подход заключается в использовании require
как вы упоминали в комментариях.
Одним из способов решения этой проблемы является регистрация дочерних контроллеров (или даже их функций обратного вызова), и тогда родитель может вызывать каждого потомка вместо широковещания.
Здесь следует отметить, что вам нужно require: "?^^parent"
- смысл, сделать обязательным необязательный и строго предковый селектор; за "^"
Angular вернул бы себя.
.directive("accordion", function(){
return {
require: ["accordion", "?^^accordion"],
controller: function(){
var children = [];
this.registerChild = function(childCtrl){
children.push(childCtrl);
}
this.handleNotification = function(){
// do something when notification arrives from parent
}
this.notify = function(){
angular.forEach(children, function(child){
child.handleNotification();
})
}
},
link: function(scope, element, attrs, ctrls){
var me = ctrls[0], parent = ctrls[1];
if (parent){
parent.registerChild(me);
}
}
}
})
Вы пытаетесь работать с угловым контекстом внутри обратного вызова jQuery, так что это не сработает. Самый простой подход к решению вашей проблемы - следующий фрагмент кода:
angular.$externalBroadcast = function (element, event, data) {
var scope = element.scope();
scope.$apply(function () {
scope.$broadcast(event, data);
});
};
И примените эту функцию внутри функции ссылки вашей директивы панели:
$scope.isOpen = !$scope.isOpen;
if($scope.isOpen){
angular.$externalBroadcast($element, 'panelOpened');
}
Посмотрите на эту модифицированную скрипку.
Вы можете добавить числовое значение depth
или аналог вашей трансляции, которая определяет иерархическую глубину, с которой была отправлена трансляция. если контроллер слушает директивы, имеет depth
из 1
выше, это может выполнить. Иначе он мог просто проигнорировать трансляцию.
Если неясно, я мог бы также добавить небольшой пример, просто дайте мне знать.