Общение с директивами братьев и сестер
Цель: создание поведения с использованием директив с обменом данными между двумя родственными элементами (каждая их собственная директива).
Поведение для использования в примере: содержимое статьи скрыто по умолчанию. Когда щелкнут заголовок, я хочу, чтобы содержимое соответствующей статьи отображалось.
Ловушка: связанные элементы статьи должны связываться друг с другом, не будучи вложенными в один родительский элемент или директиву.
<div article="article1">this is my header</div>
<div id="article1" article-content>this is content for the header above</div>
<div article="article2">this is my header</div>
<div id="article2" article-content>this is content for the header above</div>
Я знаю, что было бы проще разместить контент внутри директивы статьи, однако этот вопрос должен выяснить, как решить такую ситуацию.
Может ли директива content передавать ссылку на соответствующую директиву статьи?
Этот код не очень полезен, как сейчас, но это отправная точка. Как бы это сделать?
.directive('article', function(){
return {
restrict: "A",
controller: function($scope) {
$scope.contentElement = null;
this.setContentElement = function(element) {
$scope.contentElement = element;
}
},
link: function(scope, element) {
element.bind('click', function(){
// Show article-content directives that belong
// to this instance (article1) of the directive
}
}
}
}
.directive('articleContent', function(){
return {
require: "article",
link: function(scope, element, attrs, articleCtrl) {
// Maybe reference the article i belong to and assign element to it?
// I can't though because these are siblings.
}
}
}
Ответы
Ответ 1
Ни одна из опций директивы require
не позволит вам требовать директивы sibling (насколько я знаю). Вы можете:
- требуется элемент, используя
require: "directiveName"
- сообщите angular для поиска в дереве DOM с помощью
require: "^directiveName"
- или
require: "^?directiveName"
, если вам не нужен родительский контроллер
- или
require: "^\?directiveName"
, если вам необязательно нужна родительская оболочка DOM
Если вы хотите общаться с братом и сестрой, вам придется разместить их в каком-то родительском элементе DOM с директивным контроллером, выступающим в качестве API для их общения. Как это реализовано, в значительной степени зависит от контекста.
Вот хороший пример из Angular JS (O Reilly)
app.directive('accordion', function() {
return {
restrict: 'EA',
replace: true,
transclude: true,
template: '<div class="accordion" ng-transclude></div>',
controller: function() {
var expanders = [];
this.gotOpened = function(selectedExpander) {
angular.forEach(expanders, function(expander) {
if(selectedExpander != expander) {
expander.showMe = false;
}
});
};
this.addExpander = function(expander) {
expanders.push(expander);
}
}
}
});
app.directive('expander', function() {
return {
restrict: 'EA',
replace: true,
transclude: true,
require: '^?accordion',
scope: { title:'@' },
template: '<div class="expander">\n <div class="title" ng-click="toggle()">{{ title }}</div>\n <div class="body" ng-show="showMe" \n ng-animate="{ show: \'animated flipInX\' }"\n ng-transclude></div>\n</div>',
link: function(scope, element, attrs, accordionController) {
scope.showMe = false;
accordionController.addExpander(scope);
scope.toggle = function toggle() {
scope.showMe = !scope.showMe;
accordionController.gotOpened(scope);
}
}
}
})
Использование (шаблон нефрита):
accordion
expander(title="An expander") Woohoo! You can see mme
expander(title="Hidden") I was hidden!
expander(title="Stop Work") Seriously, I am going to stop working now.
Ответ 2
Или вы можете создать service
только для директивной связи, одним из преимуществ специального service
vs require
является то, что ваши директивы не будут зависеть от их местоположения в структуре HTML.
Ответ 3
Вышеупомянутые решения великолепны, и вы должны обязательно рассмотреть возможность использования родительской области, чтобы обеспечить связь между вашими директивами. Однако, если ваша реализация довольно проста, есть простой способ, встроенный в Angular, который может связываться между двумя областями для братьев и сестер без использования какого-либо родителя: $emit
, $broadcast
и $on
.
Скажем, например, у вас довольно простая иерархия приложений с окном поиска в навигационной панели, который входит в сложную службу, и вам нужна эта служба для трансляции результатов по различным другим директивам на странице. Один из способов сделать это будет следующим:
в службе поиска
$rootScope.$emit('mySearchResultsDone', {
someData: 'myData'
});
в некоторых других директивах/контроллерах
$rootScope.$on('mySearchResultsDone', function(event, data) {
vm.results = data;
});
Там определенная красота, насколько прост этот код. Тем не менее, важно иметь в виду, что логика emit/on/broadcast может стать очень неприятной, если у вас есть куча разных мест вещания и прослушивания. Быстрый поиск в Google может вызвать много мнений о том, когда это происходит, и не является анти-шаблоном.
Некоторая хорошая информация о emit/broadcast/on в этих сообщениях:
Ответ 4
Если список статей и его содержание мы можем сделать без какой-либо директивы, используя ng-repeat
<div ng-repeat="article in articles">
<div article="article1" ng-click='showContent=true'>{{article.header}}</div>
<div id="article1" article-content ng-show='showContent'>{{article.content}}</div>
</div>
Итак, вам нужно определить модель статьи в контроллере. Мы используем локальную область, созданную ng-repeat.
Обновить. На основе ваших отзывов вам нужно связать их вместе. Вы можете попробовать
<div article="article1" content='article1'>this is my header</div>
<div id="article1" article-content>this is content for the header above</div>
и в вашей директиве
использование
link: function(scope, element,attrs) {
element.bind('click', function(){
$('#'+attrs.content).show();
}
}
И последним методом может быть использование методов $rootScope.$broadcast
и scope.$on
для связи между контроллерами. Но в этом подходе вам нужно отслеживать, откуда пришло сообщение, и кто является предполагаемым получателем, которому необходимо его обработать.
Ответ 5
У меня была одна и та же проблема, и я смог ее решить.
Чтобы получить одну директиву, чтобы скрыть другие директивы sibling, я использовал родительскую директиву, чтобы действовать как API. Одна дочерняя директива сообщает родителю, что он не должен отображаться/скрываться, передавая ссылку на его элемент, а другой дочерний вызов вызывает функцию переключения родителя.
http://plnkr.co/edit/ZCNEoh
app.directive("parentapi", function() {
return {
restrict: "E",
scope: {},
controller: function($scope) {
$scope.elements = [];
var on = true;
this.toggleElements = function() {
if(on) {
on = false;
_.each($scope.elements, function(el) {
$(el).hide();
});
} else {
on = true;
_.each($scope.elements, function(el) {
$(el).show();
});
}
}
this.addElement = function(el) {
$scope.elements.push(el);
}
}
}
});
app.directive("kidtoggle", function() {
return {
restrict: "A",
require: "^parentapi",
link: function(scope, element, attrs, ctrl) {
element.bind('click', function() {
ctrl.toggleElements();
});
}
}
});
app.directive("kidhide", function() {
return {
restrict: "A",
require: "^parentapi",
link: function(scope, element, attrs, ctrl) {
ctrl.addElement(element);
}
}
});
Ответ 6
У меня была такая же проблема с директивой select all/select, которую я писал. Моя проблема была в том, что флажок "Выбрать все" был в строке заголовка таблицы, а элемент "Выбор" был в теле таблицы. Я обошел это, выполнив службу pub/sub notification, чтобы директивы могли разговаривать друг с другом. Таким образом, моя директива не волновала, как мой htlm был структурирован. Я действительно хотел использовать свойство require, но использование службы работало точно также.