Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Communication between nested directives

I'm trying to communicate between a parent directive and its nested child directive, and the other way. I've managed to achieve this by using $broadcast and $emit, but because I'm passing in some arguments to the directives I've had to create isolated scope on my directives, so in order for the $broadcast/$emit to work I have to broadcast 'up a level' on the parent scope (scope.$parent.$broadcast). Now the broadcast is no longer just going to the nested child, but to all directives at the same level, which I don't want. I've created a plunker to show the issue, here. What I need is for when one of the buttons is pressed, only the child directive to receive the broadcast message, and vice-versa. Am I missing something, or is this not possible when using isolated scope?

In my HTML:

<body ng-app="myApp">
  <directive1 data-title="Click me to change name">
    <directive2 data-name="John Smith"></directive2>
  </directive1>

  <directive1 data-title="Click me to change this other name">
    <directive2 data-name="Gordon Freeman"></directive2>
  </directive1>
</body>

Directive 1:

<div>
  <button ng-click="changeName()">{{title}}</button>
  <div ng-transclude></div>
</div>

Directive 2:

<div>
  <h2>{{name}}</h2>
</div>

My directives:

myApp.directive('directive1', function(){
  return {
    restrict: 'E',
    replace: true,
    templateUrl: 'Directive1.html',
    transclude: true,
    scope: {
      title: '@'
    },
    link: function(scope, elem){
      scope.changeName = function() {
        scope.$parent.$broadcast('ChangeName');
      };

      scope.$parent.$on('NameChanged', function(event, args){
        scope.title = 'Name changed to ' + args;
      });
    }
  }
});


myApp.directive('directive2', function(){
  return {
    restrict: 'E',
    replace: true,
    templateUrl: 'Directive2.html',
    scope: {
      name: '@'
    },
    link: function(scope, elem){
      scope.$on('ChangeName', function(event, args){
        scope.name = 'Adam West';

        scope.$emit('NameChanged', 'Adam West');
      });
    }
  }
});
like image 764
Ben Heymink Avatar asked Oct 01 '22 02:10

Ben Heymink


1 Answers

There are 5 main ways to communicate between directives:

1) A common service.

This is not a good solution for you because services are always singletons and you want to have multiple unique directives. You could only use a service if you maintained a dictionary of parents and children in the service and managed routing the calls to the right associated parent / child, which is the same problem you have using events.

2) Events.

If you can't limit the event to the correct part of the DOM / tree via broadcasting from the right node, you'll have to add a unique identifier to the event. In this case, if you are broadcasting from the root and / or multiple children are receiving the message, give each parent / child a unique identifier and add this to the emit / broadcast / on. This is not a pretty solution, but it will work.

3) One-way bindings.

The '&' binding in an isolated scope lets you pass parent functions into a child. The child can then call these functions on the parent scope to notify the parent of changes. This is great for child - parent communication but doesn't go the other way. You could combine this solution with broadcasting an event from the parent to communicate to the child.

4) Two-way bindings.

Sometimes you can use a attribute on an isolated scope to pass information or flags back and for between the parent-child. This doesn't work in your example because the parent doesn't know anything about his child since the child is injected via transclusion.

5) require parent controller.

A directive can use the require property to specify that another directive is required to exist either as a parent or on the same element. You can't require sibling directives. The required directives must have controllers defined. The controller is then passed to the link (or compile) function and you can call functions on the controller. This can be used to allow communication between the directives. In your example, if directive2 required directive1 you could set functions like addChild() to the controller. The child (directive2) would then add itself to the parent who could update / call all children when changeName was called.

myApp.directive('directive1', function(){
  return {
    // ...
    controller: function($scope) {
      $scope.children = [];
      this.addChild = function(child) {
        $scope.children.push(child);
      }
    },
    link: function(scope, elem){
      scope.changeName = function() {
        _.each(scope.children, function(child) {
          child.setName('Adam West');
        };
      };
    },
  }
});


myApp.directive('directive2', function(){
  return {
    // ...
    require: "^directive1", // require directive1 as a parent or on same element
    link: function(scope, elem, attributes, directive1Controller){
      child = {
        setName: function(name) {
          scope.name = name;
        },
      };

      directive1Controller.addChild(child);
    }
  }
});
like image 146
Josh G Avatar answered Oct 05 '22 13:10

Josh G