AngularJS - Directives (Contd.)

AngularJS: Directive to Directive Communication

After explaining how to create our custom directives and their several properties, we'll be moving on to talk about how directives can be set to exchange information among themselves.

As I have more faith in working examples, I will put up one for this too.

Task description

You will come across another property of the directives, which is, controllers of their own. This helps them to manage the scope they are working on. Sometimes, it's the scope of parent elements and sometimes it's isolated. We shall talk about isolated scopes in the next article. Here, we will declare a controller for our directive and will make it work as an API to talk to other directives. I'll let the example do the talking as in the following:

  1. var myApp = angular.module('superApp', []);  
  2.   
  3. myApp.directive('superhero'function() {  
  4.     return {  
  5.         restrict: 'E',  
  6.         scope: {},  
  7.         controller: function($scope) {  
  8.             $scope.abilities = [];   
  9.             this.addStrength = function() {  
  10.                 $scope.abilities.push("Strength");  
  11.             };  
  12.         },    
  13.         link: function(scope, element) {  
  14.             element.bind("mouseenter"function() {  
  15.                 alert(scope.abilities);  
  16.             })  
  17.         }  
  18.     };  
  19. });  
  20.   
  21. myApp.directive('strength'function() {  
  22.     return {  
  23.         require: "superhero",  
  24.         link: function(scope, element, attrs, superheroCtrl) {  
  25.             superheroCtrl.addStrength();  
  26.         }  
  27.     };  
  28. }); 

Apart from a directive's own controller, you will notice another directive property "require" in the second directive I declared. This enables the directive to communicate with the other ones. All I had to do was:

  1. Mention the name of directive I want to talk to.

  2. Add the reference to its controller as the fourth parameter in the linking function. You can name it anything instead of "superheroCtrl" and Angular will still pick it up as the controller name just because it is the fourth parameter.

Using these directives in our template:

  1. <div ng-app="superApp">  
  2.     <superhero strength>Ironman</superhero>  
  3. </div> 

When the "strength" attribute is added as a behavior for the element "superhero", it uses the function of the superhero controller to push a string inside its array. This array is displayed as an alert message when we roll the mouse over this "superhero" element, as expected.

So we can say that our directives are talking to each other now.

Now, the problem.

If I try to add another "superhero" element, the scope array "abilities" for the first element is over-written by the second element. You can see the example here: jsfiddle

In this example, the "superhero" controller acts like an API that is capable of pushing two different strings in the array "abilities". So for the first element, I have tried to push “flight” into the abilities array and “strength” in the second element. I now expect an alert message saying “flight” when the mouse is rolled over the first element and the message “strength” when rolled over the second one.

This doesn't happen though.

Wondering what might the reason be? The answer is: both the elements share the same scope, that is the parent scope. So the array "abilities" for the parent scope is over-written when the second element is rendered. This is where "isolated" scopesare relevant. It helps in providing individual scopes to the directives that have nothing to do with the parent scopes. You will see how: jsfiddle

AngularJS – Transclusion

So what happens when you have a custom component, for example, called "panel" and it contains another component, say, a button. Something like:

  1. <div ng-app="myApp">  
  2.     <panel>  
  3.         <button>Click</button>  
  4.     </panel>  
  5. </div> 

The button is simply rendered inside the component "panel" and work as expected. See: jsfiddle

What if this component or directive named "panel" has its own template defined already? Is it going to care about what you write inside the "panel" in your template? The answer is no. You will have a better idea when you look at this: jsfiddle

You will note that the button is not rendered this time. It is over-written by the content of the template defined for the directive.

The button can also be shown along with the directive's template. Transclusion makes it possible. What does it exactly do? It just yanks the content out of the component and pastes it where ever we want it to be pasted inside our template. This is how it is done:

  1. <div ng-app="myApp">  
  2.     <div ng-controller="myCtrl">  
  3.         <panel>  
  4.             <div class='other'>Another component</div>  
  5.         </panel>  
  6.     </div>  
  7. </div>  
  8.   
  9. var myApp = angular.module('myApp', []);  
  10.   
  11. myApp.controller('myCtrl'function ($scope) {});  
  12.   
  13. myApp.directive('panel'function () {  
  14.     return {  
  15.         restrict: 'E',  
  16.         transcludetrue,  
  17.         template: "<div class='panel'> This is the content of panel <div ng-transclude></div></div>"  
  18.     };  
  19. }); 

All I had to do was set the "transclude" property to "true" and the element with the "ng-transclude" attribute defines that the content is to be pasted inside this element. See it working here: jsfiddle

Previous article: AngularJS Directives : Part 6