23

目标:使用指令创建行为,并在 2 个兄弟元素(每个元素都有自己的指令)之间进行通信。

示例中使用的行为:默认情况下隐藏文章内容。单击标题时,我希望显示相关文章内容。

问题:相关的文章元素需要相互关联,而不是嵌套在单个父元素或指令中。

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

我知道将内容放在文章指令中会更容易,但是这个问题是要找出如何解决这样的情况。

内容指令能否以某种方式将自身传递给相关的文章指令?

这段代码不像现在这样有用,但它是一个起点。我将如何做到这一点?

.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.
    }
  }
}
4

6 回答 6

32

没有任何指令require选项允许您要求兄弟指令(据我所知)。您只能:

  • 要求元素,使用require: "directiveName"
  • 告诉 Angular 使用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.
于 2013-08-15T03:11:04.380 回答
10

或者您可以创建一个仅用于指令通信,special vs的service一个优点是您的指令将不依赖于它们在 html 结构中的位置。servicerequire

于 2014-05-19T17:42:16.993 回答
3

上述解决方案很棒,您绝对应该考虑使用父范围来允许指令之间的通信。但是,如果您的实现相当简单,那么 Angular 中内置了一个简单的方法,它可以在两个同级作用域之间进行通信,而无需使用任何父作用域:$emit、、$broadcast$on

举例来说,你有一个非常简单的应用层次结构,带有一个导航栏搜索框,可以接入一个复杂的服务,你需要该服务将结果广播到页面上的各种其他指令。一种方法是这样的:

在搜索服务中

$rootScope.$emit('mySearchResultsDone', {
  someData: 'myData'
}); 

在其他一些指令/控制器中

$rootScope.$on('mySearchResultsDone', function(event, data) {
  vm.results = data;
});

这段代码有多简单,有一定的美感。但是,重要的是要记住,如果您有很多不同的地方广播和收听,那么发出/开启/广播逻辑会很快变得令人讨厌。快速的谷歌搜索可以找到很多关于它何时是和不是反模式的意见。

在这些帖子中对发射/广播/开启有一些很好的了解:

于 2015-08-11T00:15:27.487 回答
0

If there is a list of articles and its content we can do it without any directive, using 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>

So you need to define the article model in controller. We are making use of local scope created by ng-repeat.

Update: Based on your feedback, you need to link them together.You can try

<div article="article1" content='article1'>this is my header</div>
<div id="article1" article-content>this is content for the header above</div>

and in your directive

use

link: function(scope, element,attrs) {
      element.bind('click', function(){
        $('#'+attrs.content).show();
      }
    }

And the final method could be to use $rootScope.$broadcast and scope.$on methods to communicate between to controllers. But in this approach you need to track from where the message came and who is the intended recipient who needs to process it.

于 2013-08-15T02:39:23.327 回答
0

我有完全相同的问题,我能够解决它。

为了让一个指令隐藏其他同级指令,我使用了一个父指令作为 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);
    }
  }  
});
于 2014-11-05T22:45:48.173 回答
0

我正在编写的全选/选择项目指令也有同样的问题。我的问题是全选复选框位于表格标题行中,而选择项位于表格正文中。我通过实现一个 pub/sub 通知服务来解决这个问题,这样指令就可以相互通信。这样我的指令就不关心我的 htlm 的结构。我真的很想使用 require 属性,但使用服务也同样有效。

于 2014-12-17T17:13:27.590 回答