与兄弟指令沟通

时间:2013-08-15 02:26:36

标签: angularjs angularjs-directive

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

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

catch:相关的article元素需要彼此关联,而不是嵌套在单个父元素或指令中。

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

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

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

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

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

6 个答案:

答案 0 :(得分:32)

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

  • 使用require: "directiveName"
  • 对元素进行要求
  • 告诉angular使用require: "^directiveName"
  • 搜索DOM树
  • 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.

答案 1 :(得分:10)

或者您可以仅为指令通信创建service,特殊service vs require的一个优点是您的指令不会依赖于它们在html结构中的位置。

答案 2 :(得分:3)

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

例如,假设你有一个非常简单的应用程序层次结构,其中有一个导航栏搜索框可以访问复杂的服务,你需要该服务将结果广播到页面上的各种其他指令。一种方法是这样的:

在搜索服务中

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

在其他一些指令/控制器

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

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

对这些帖子中的emit / broadcast / on有一些很好的见解:

答案 3 :(得分:0)

如果有文章列表及其内容,我们可以在没有任何指令的情况下使用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.$broadcastscope.$on方法在控制器之间进行通信。但是在这种方法中,您需要跟踪消息的来源以及需要处理消息的目标接收者。

答案 4 :(得分: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);
    }
  }  
});

答案 5 :(得分:0)

我在写一个select all / select item指令时遇到了同样的问题。我的问题是select all复选框位于表标题行中,select项目位于表格主体中。我通过实现发布/订阅通知服务来解决它,因此指令可以相互通信。这样我的指令就不关心我的htlm的结构。我真的很想使用require属性,但使用服务的效果也一样。