2

All:

Suppose I have two directives( dir1 and dir2) , which are both isolated scope. From some posts, I learnt that I need to use "require" to get scope of the other directive, but there is one question confused me so much:

Suppose I use ng-repeat generated a lot of dir1 and dir2, how do I know in certain dir1, which specific dir2's controller scope is required( cos there are many dir2 and in my understanding, all those scopes in dir2 controller are independent to each other)?

For example:

app.directive("dir1", function(){
    var counter = 0;
    return {
        restrict:"AE",
        scope:{},
        template: "<button class='dir1_btn'>highlight dir2_"+(couter++)+" </button>",
        link:function(scope, EL, attrs){
             EL.find("button.dir1_btn").on("click", function(){
                 // How to let according dir2 know this click?
             })
        }
    }
}) 

app.directive("dir2", function(){
    var counter = 0;
    return {
        restrict:"AE",
        scope:{},
        template: "<span class='dir2_area'>This is dir2_"+(couter++)+" </span>",
        link:function(scope, EL, attrs){
             // Maybe put something here to listening the event passed from dir1?
        }
    }
}) 

And the html like( for simple purpose, I just put 2 of each there, actually it will generated by ng-repeat) :

<dir1></dir1>
<dir2></dir2>
<dir1></dir1>
<dir2></dir2>

Consider this just like the switch and light, dir1 is the switch to open(by change background-color) according light (dir2).

In actual project, what I want to do is angularJS directive version sidemenu and scrollContent, each item in sidemenu is a directive, click it will make according content(another directive) to auto scroll to top.

I wonder how to do this? I know this is easy in jQuery, just wondering how to hook this into AngularJS data-driven pattern.

Thanks

Kuan
  • 11,149
  • 23
  • 93
  • 201

2 Answers2

1

You may have to use some sort of strategy. Some kind of identifier hook up. Clearly you cannot use require(to require the controller of a directive and you don't have any also it can only look up to ancestors or itself not siblings). For example you could add an id attribute and a for attribute and target the element with a selection based on specific attribute value and fire an event. With this position of related element does not matter.

Your directive could look like:

  <dir1 dir-for="id1"></dir1>
  <dir2 dir-id="id1"></dir2>

  <dir1 dir-for="id2"></dir1>
  <dir2 dir-id="id2"></dir2>

and simple implementation:

.directive("dir1", function($document) {
  var counter = 0;
  return {
    restrict: "AE",
    scope: {
      dirFor: '@'
    },
    template: "<button class='dir1_btn' ng-click='handleClick()'>highlight dir2_({{dirFor}}) </button>",
    link: function(scope, EL, attrs) {

      var $target = angular.element(
                $document[0].querySelector('[dir-id="' + scope.dirFor + '"]'))
               .contents().scope();

      var clicked = false;

      scope.handleClick = function() {
        clicked = !clicked;
        targetScope.$broadcast("SWITCH_CLICKED", clicked);
      }

      scope.$on('$destory',function() {
        $target = null;
      }
    }
  }
})

app.directive("dir2", function() {
  var counter = 0;
  return {
    restrict: "AE",
    scope: {
      dirId: '@'
    },
    template: "<span class='dir2_area' ng-class=\"{true:'on', false:'off'}[status]\">This is dir2_{{dirId}}</span>",
    link: function(scope, EL, attrs) {
      console.log(scope.$id);
      scope.status = false;
      scope.$on('SWITCH_CLICKED', function(e, data) {

        scope.status = data;
      });
    }
  }
});

Demo

var app = angular.module('app', []).controller('ctrl', angular.noop);

app.directive("dir1", function($document) {
  var counter = 0;
  return {
    restrict: "AE",
    scope: {
      dirFor: '@'
    },
    template: "<button class='dir1_btn' ng-click='handleClick()'>highlight dir2_({{dirFor}}) </button>",
    link: function(scope, EL, attrs) {

      var $target = angular.element($document[0].querySelector('[dir-id="' + scope.dirFor + '"]')).contents();

      var clicked = false;

      scope.handleClick = function() {
        clicked = !clicked;
        $target.scope().$broadcast("SWITCH_CLICKED", clicked);
      }

      scope.$on('$destroy',function() {
        $target = null;
      });
    }
  }
})

app.directive("dir2", function() {
  var counter = 0;
  return {
    restrict: "AE",
    scope: {
      dirId: '@'
    },
    template: "<span class='dir2_area' ng-class=\"{true:'on', false:'off'}[status]\">This is dir2_{{dirId}}</span>",
    link: function(scope, EL, attrs) {
      
      scope.status = false;
      
      scope.$on('SWITCH_CLICKED', function(e, data) {
     
        scope.status = data;
      });
    }
  }
})
.on{
color:green;
}
.off{
color:blue;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
  <dir1 dir-for="id1"></dir1>
  <dir2 dir-id="id1"></dir2>

  <dir1 dir-for="id2"></dir1>
  <dir2 dir-id="id2"></dir2>

</div>

I have used $document[0].querySelector('[dir-id="' + scope.dirFor + '"]')).contents().scope() to get hold of the scope, similarly you could do .controller to get hold of the controller instance as well. Current example is doing an absolute selection(with document), you could as well make it relative.

PSL
  • 123,204
  • 21
  • 253
  • 243
  • thanks for reply. I am wondering what I am supposed to see in your Demo answer? I click the button, it seems nothing changed... – Kuan Jun 09 '15 at 18:31
  • thanks, let me work on it for a little while to understand the whole thing first. – Kuan Jun 09 '15 at 18:39
  • oh, I C, so basically we use jQuery manually find out according element, and take out its scope and trigger it broadcast to make the style change, right? I am wondering if there is any other more fancier designs which is by only manipulate data and let angularJS data driven to handle style? I just feel that the dir1 has some very tight couple with dir2 which is hard for reusing with other dir3 or dir4... – Kuan Jun 09 '15 at 18:48
  • @Kuan no need to use jquery. i am using native selector, otherwise what you said is correct. if you want to only manpulate data you could access specific property from `scope()` itself. – PSL Jun 09 '15 at 18:51
1

The most important thing to note here is that I think you want to use ng-class Since you are creating both directives in an ng-repeat, I assume you are iterating over a list of objects (even if they are two separate ng-repeats, if you iterate over the same list of objects in both it will work. JQuery should not be necessary)? Attach an ngClass object to each object you iterate over, put it on an ng-class attribute in your dir2, then give dir1 access to change it. ngClass provides animation hooks if you want to animate the transition. The rest of my answer may help, though I would like to redo it now that I thought of ng-class. I have to get back to work for now though. I'll watch for feedback and try to answer quickly if you have questions.

I think there are probably a few ways to better accomplish what you are trying to do. It is not clear why both of your directives need to have isolate scopes. As I use angular more I find that though isolating a scope is a powerful technique, it is best to avoid over using it.

As for the require directive property, this post explains how to make directives communicate via their controllers very well.

I have two possible suggestions for you. Make it one directive Why can't you just put the templates into one?

Or if as I assume there is some reason they need to be separate, you could consider just sharing an object between them.

<div ng-repeat='obj in sharedDirObjs'>
  <dir1 shared-dir-obj='obj'></dir1>
  <dir2 shared-dir-obj='obj'></dir2>
</div>

app.controller('ctrl', function() {
  $scope.sharedDirObjs = [obj1, obj2, obj3]
});

app.directive("dir1", function(){
var counter = 0;
return {
    restrict:"AE",
    scope:{sharedDirObj : '='},
    template: "<button class='dir1_btn' ng-click='clickFn()'>highlight dir2_"+(couter++)+" </button>",
    link:function(scope, EL, attrs){
       var dir1vars...     
       scope.clickFn = function(){
             // dir1 logic...
             scope.sharedDirObj.dir2.clickFn(dir1vars...);
       };
    }
}}) 

app.directive("dir2", function(){
    var counter = 0;
    return {
        restrict:"AE",
        scope:{sharedDirObj : '='},
        template: "<span class='dir2_area'>This is dir2_"+(couter++)+" </span>",
        link:function(scope, EL, attrs){
             scope.sharedDirObj.dir2 = {};
             scope.sharedDirObj.dir2.clickFn(dir1vars...) {
                 // access to dir2 vars
             };
        }
    }})

Similarly, you could create a service that holds an array of objects that are shared by injecting the service and indexed using the $index from the ng-repeat, or you could use an id system as PSL suggests. Note that the solution I describe above could work with isolate scope, or without it using scope.$eval(attr.sharedObj); on either or both of your directives. The answer to this question provides a solid runthrough of when and why to use isolated scope. In any case it would likely be best not to pipe functions through a shared object as I am showing above, and timing issues would need to be dealt with. Better would be to store properties on the object and set a scope.$watch on them in your dir2.

Community
  • 1
  • 1
Kyle Zimmer
  • 300
  • 2
  • 7
  • Thanks, so your way is using parent scope as a medium to coordinate the communication between two children directive by setting handler from on side and calling from the other? – Kuan Jun 09 '15 at 20:11
  • That was my initial idea, and I still think using the parent scope as a medium is how I would do it, but setting the handler from the other side seems less than ideal in retrospect. It would be better to define the handler in the directive it is called from and have it act on data that is shared via the parent scope. That way you don't have to worry about dir1 trying to call a function that has yet to be defined by dir2. ng-repeating over the same list of objects and using ng-class is the important part of how I think I would actually do it. – Kyle Zimmer Jun 09 '15 at 20:46