0

I'm trying to get two directives to communicate with each other via their (inner defined) controllers, but I'm new to angular and still not clear on a couple of things.

Essentially, I just want two separate directives where, when you click on an element inside directive A, some function within B executes; I want to tell B to update itself, based on what happens in A. What I've got so far:

app.directive('A', function(){
    return{
        restrict: 'E',
        require:'B',
        templateUrl:'a.html',
        link: function(scope, element, attr, bController){
            //should I run B's behavior here?
            //how? can't reach B's controller
        },
        controller: function(){
            //or here?
            //how do I access B's controller here?
        },
        controllerAs:'A'
    };
});

app.directive('B', function(){
    return{
        restrict: 'E',
        templateUrl:'b.html',
        link: function(scope, element, attr){

        },
        controller: function(){
            //here there'll be functions that manipulate
            //elements inside this directive (B)
        },
        controllerAs:'B'
    };
});

I'm getting an error, since A is trying to find the controller named B, but it's a directive: https://docs.angularjs.org/error/$compile/ctreq

Also, should I manipulate elements from within the link function, or the directives controller? Even after reading, I'm still a bit fuzzy on link vs controller; the way I see it: link is like a constructor of sorts, whereas the controller is where behavior goes.

So does that mean I always place DOM manipulation inside of the controller?

Is there any other way to approach this? I've read a bit about $scope, but still not entirely clear on that either.

cintron
  • 513
  • 5
  • 19
  • https://egghead.io/lessons/angularjs-directive-to-directive-communication might help you. – micronyks Jul 14 '14 at 16:06
  • Actually, I've been watching them! They definitely help, but I feel as if he didn't go deep enough (at least in that video). He only talks about `link` functions, but not controllers. – cintron Jul 14 '14 at 16:10
  • I think he has done pretty good job. Okay no problem. look this way can't help you. come up with task or target then can help you. – micronyks Jul 14 '14 at 16:13
  • are you confused with link: and controller: functions of single directive ?? OR communication of two directives? – micronyks Jul 14 '14 at 16:18

2 Answers2

3

You can only require directives in parent elements or in the same element. You're probably using B inside A, or vice verse.

If that's the case, you have one directive being used inside the other, you can go to the inner direcive and require the outer by using: require: '^A'. The ^ means it can be directive from your parent.

If the directives are siblings, you could build a third directive that would parent them both, and required it from inside both and they would use this medium to communicate:

app.directive('C', function() {
  return {
    controller: function() {
      this.registerPartyA = function(partyA) {
        // ...
      };

      this.registerPartyB = function(partyB) {
        // ...
      };
    }
  }
});

Then from your directives;

app.directive('B', function() {
  return {
    require: '^C',
    link: function(scope, elm, attr, cCtrl) {
      cCtrl.registerPartyB(...);

      // maybe when user clicks you do
      // cCtrl.aCtrl.doSomething()
    }
  };
});

If they're not related anyhow, you could still use a service and inject the service in both directives, and use this as a common place.

Finally, if both directives share the same scope (are under the same element, or none of the directives require isolated/children scopes) you could simply declare the method directly inside the $scope and simply call it from the other directive:

app.directive('A', function() {
  return {
    controller: function($scope) {
      $scope.declaredByA = function() {
        // ...
      };
    }
  };
});

app.directive('B', function() {
  return {
    controller: function($scope) {
      $scope.clicked = function() {
        $scope.declaredByA();
      };
    }
  };
});

And a last call, you could use $scope.$broadcast and $scope.$emit to do the communication.

I would try in the order I've mentioned, as long as you meet the requirements.

Community
  • 1
  • 1
Caio Cunha
  • 23,326
  • 6
  • 78
  • 74
1

From the AngularJS documentation for controllers:

Use controllers to:

  • Set up the initial state of the $scope object.
  • Add behavior to the $scope object.

Do not use controllers to:

  • Manipulate DOM — Controllers should contain only business logic. Putting any presentation logic into Controllers significantly affects its testability. Angular has databinding for most cases and directives to encapsulate manual DOM manipulation.
  • Format input — Use angular form controls instead. Filter output — Use angular filters instead.
  • Share code or state across controllers — Use angular services instead.
  • Manage the life-cycle of other components (for example, to create service instances).

From the AngularJS documentation for directives:

Directives can be thought of as markers on a DOM element (such as an attribute, element name, comment or CSS class) that tell AngularJS's HTML compiler ($compile) to attach a specified behavior to that DOM element or even transform the DOM element and its children.

So, just to make things clear, no you should never manipulate the DOM from within a controller. You should always use the directives link function to manipulate the DOM.

If you want your controllers to share state information with one another, you should consider adding an angular Service (documentation here). Service are objects that are instantiated once, and whose state is preserve across the module. They can be injected into controllers.

Another option is to implement some $emit and $on event raisers and listeners. This would allow you to communicate across your controllers using events to signal state changes. Here is another SO post that explains this better than I could: Working with $scope.$emit and $scope.$on.

Hope this helps!

Community
  • 1
  • 1
pje
  • 2,458
  • 1
  • 25
  • 26
  • Thanks for the answer! I think a service is what I'm looking for. On another note, what if I want to set up a button, that when clicked, toggles something somewhere else (another directive for example). Should that go inside a controller or link? – cintron Jul 14 '14 at 16:36
  • But how would that `var` be available to the other directive? Let's say these directives are siblings inside the body: how would toggling said `var` in directive A be available to it's sister directive B? Isn't the `var` defined within A's controller and only available to A? – cintron Jul 14 '14 at 17:05
  • Sorry, I accidentally posted that last comment before I was finished. Here it is again: What you would want to do, is inside of a controller create a boolean variable. Then reference that inside an `ng-click` directive like this: `
    ...
    `. This will cause the boolean `toggleVariable` to be toggle true or false, depending on the current value. Then, you can use that variable elsewhere to toggle something on/off. Here is a plunker with a running example: http://plnkr.co/edit/8o3Jl9Wk2qhh1AXYyLjO?p=preview
    – pje Jul 14 '14 at 17:09
  • 1
    And yes, to answer your other question it would only be available within the `$scope` of the controller it is defined within. You can get around this by creating a parent controller that encapsulates both of the controllers that are defined in your directives. You would then include an **isolate scope** in your directives, and pass the toggle variable into your isolate scope by reference `&`. This would allow you to have access to the parent scope variable in both controllers. Google the documentation for directives, and then search for isolate scope. – pje Jul 14 '14 at 17:11
  • On another note, should I manipulate my styles/animations in the controller or the link function? Does that even count as DOM manipulation? – cintron Jul 15 '14 at 17:10
  • 1
    @yoaquim consider investigating the `ng-style` directive – pje Jul 15 '14 at 17:27