Custom Angular directive broadcasting an event but not to nested children?

I have created an accordian directive in Angular which can be nested, so an accordian can have child accordians inside them.

I want to broadast an event when the accordian opens and closes so that other directives can listen for it (e.g. a menu inside an accordian panel might reveal itself when the accordian it's inside is opened).

The thing is that if there is a nested inner accordian inside the outer one I don't want the event to be broadcast to the inner accordian's child elements because the inner accordian hasn't broadcast an open/close event.

Just in case that makes no sense, to put it another way an element inside a nested accordian should be able to listen to an open/close event broadcast by the accordian it is in, not the one further up the DOM tree.

Hopefully there is a simple solution to this.

Update: added demo here: http://jsfiddle.net/jonhobbs/xr1kLqba/

I clearly haven't understood $broadcast and $on properly as the demo events currently aren't working at all, but it should be clear what I'm trying to do!

Edit: Apparently all links to jsfiddle must be accompanied by code, so here is some.

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' });
      });
    }
  };
});

Answers


By far, the easiest approach is to use require as you alluded to in the comments.

One way to address this is to register children controllers (or even their callback functions), and then the parent can call into each child instead of broadcasting.

The thing to note here is that you would need to require: "?^^parent" - meaning, make the require optional and strictly-ancestor selector; for "^" Angular would return self.

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

Yo're trying to work with angular context inside of jQuery callback, so that's will not work. The easiest approach to solve your problem is the following code snippet:

angular.$externalBroadcast = function (element, event, data) {
    var scope = element.scope();
    scope.$apply(function () {
        scope.$broadcast(event, data);
    });
};

And apply this function inside link function of your panel directive:

$scope.isOpen = !$scope.isOpen;                
if($scope.isOpen){
    angular.$externalBroadcast($element, 'panelOpened');
}

Have a look at this modified Fiddle.


You could add a number value depth or similar to your broadcast, that defines the hierarchical depth the broadcast was sent from. if the directives controller listening, has a depth of 1 higher, it could execute. Else it could just ignore the broadcast.

If it's unclear, I could also add a small example, just let me know.


Need Your Help

SQL Azure connection string speed (IP or domain)

sql azure connection-string azure-sql-database

Until now we always use an IP address in our connection string to the SQL. This way we think it will be faster because the user doesn't need to find the IP of the domain.

"Session closed" error when trying to get a blob from DB using getBinaryStream()

java oracle hibernate

I'm using Hibernate 3.1 and Oracle 10 DB. The blob is defined as @Lob @Basic @Column in the Hibernate entity which corresponds to the relevant DB table.