Last update:
Angular JS directive communication guide – part 2
In the previous post I wrote about common cases of directive communication. Now’s the time for several slightly more complicated scenarios: accessing child elements, siblings and issues with transclusion.
Accessing a directive on a child element
1 2 3 |
<div client> <div server></div> </div> |
Although the official docs don’t mention this, this is a valid use case. It’s just a bit trickier than the opposite (caller below the callee). It comes from the fact that you can’t just require
a child directive (similarly to ^server
). It is possible to overcome this limitation by passing the child’s controller to the parent. The other option is to use events.
Registering a child controller
Since we can’t require a child element, we have to pass it to the parent directive. In the following example the parent’s controller exposes a register
function which accepts the child’s controller as a parameter. To use it, the child directive must require the parent in the usual way (by require: "^client"
) and provide its controller in the link function. However, by default, the directive controller is not accessible from its link function. We can either require
it (which IMHO feels a bit weird – to require a directive by itself) or use the controllerAs
field to expose it in the scope.
Here’s an example with require:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
app.directive("server", function() { return { // It is perfectly valid for the directive to require itself. // This allows to use the controller in the linking function. require: ["server", "^client"], controller: function() { this.log = function(message) { console.log(message); }; }, link: function($scope, $elem, $attrs, controllers) { // The require field contains an array, so the fourth parameter // of the linking function is also an array. var serverController = controllers[0]; var clientController = controllers[1]; clientController.register(serverController); } }; }); app.directive("client", function() { return { controller: function($scope) { this.register = function(serverController) { $scope.serverController = serverController; }; }, link: function($scope) { $scope.serverController.log("Hello, this is client!"); } }; }); |
And the serverDirective part using controllerAs syntax:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
app.directive("server", function() { return { require: "^client", controller: function() { this.log = function(message) { console.log(message); }; }, // controllerAs publishes the controller in the scope controllerAs: "controller", link: function($scope, $elem, $attrs, clientController) { clientController.register($scope.controller); } }; }); |
See the full example on JSBin.
Events
The directives can communicate by events the same way it was described in the “Accessing a directive on a parent element” case in the previous post.
Accessing a sibling directive
1 2 |
<div server></div> <div client></div> |
This is perhaps the least supported scenario. The directives don’t know anything about each other. Thus, the only possibility we have is to use events. However, if both directives create new scopes, neither $emit
nor $broadcast
will work ($emit
goes up the scope hierarchy, $broadcast
goes down; there’s nothing that goes sideways ;)). Fortunately, we can utilize their common parent to broadcast an event. Accessing the parent scope is possible through the scope’s $parent
field:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
app.directive("server", function() { return { scope: true, link: function($scope) { $scope.$on("message", function(e, msg) { console.log(msg); }); } }; }); app.directive("client", function() { return { scope: true, link: function($scope) { $scope.$parent.$broadcast("message", "Hello, this is client!"); } }; }); |
See the full example on JSBin.
Problems with transclusion
Both parent-child cases become more complicated when the parent defines a template. As the template is applied before the child directive is compiled, the child’s controller or link function won’t even have a chance to be called.
1 2 3 |
<div parent> <div child></div> </div> |
1 2 3 4 5 |
app.directive("parent", function() { return { template: "<strong>I'm the parent</strong>" }; }); |
The parent directive must use transclusion – only then the child will be compiled and executed. This example shows how to achieve this without effort. Transcluded directives can require their parents as usual, so no additional work would be required here but use of events won’t be as intuitive as in the previous examples. See, a directive creates another scope for the transcluded content, but it is not a child of the original directive’s scope. Instead it’s its sibling. There are many great resources on the web describing this behavior (such as this or this), so I won’t go into details here. The point to remember is that there is no parent-child relationship between the two directives’ scopes. This changes the parent-child case into the sibling case, described above.
Other cases
Finally, there may be a situation that doesn’t fall into any of the categories described here (i.e. two directives in completety different parts of the DOM). We can’t rely on the require
feature, so the only way to go is to use events. But simple $broadcast
or $emit
may not be enough if one directive is not a parent of the other. The safest method is to $broadcast
an event from the root scope. This way the event will be received by every scope in the application and it’s up to the developer to decide which scopes will handle it.
1 |
$scope.$root.$broadcast("doSomething"); |
The root scope may be accessed by the $root
field of any scope.
Conclusion
In these two posts I summarized the methods of communication between directives in AngularJS. Please let me know if this works for you. Also, don’t hesitate to tell me if you spot any errors or want to know more about this topic.
have you tried using a publish/subscribe pattern to communicate b/w directives that arent in the same DOM or sibling elements?
Sure, that’s similar to what I described in Part 1, but since we don’t know where the other directive is placed I’d go with
$rootScope.$broadcast()
to send the event to all scopes.hai, thanks for your great post!!! really helpful.
i want to know, how the ‘broadcast’ pattern is efficient than ‘publish/subscribe’ pattern?
one advantage is, it makes directives as a reusable or i can say it is more independent.
at the same time, in big applications, it will be broadcast to all the directives will consume lot of process and time, is it correct?
Hi Rajesh,
scope.$broadcast
actually uses pub/sub pattern. Only the scopes that listen for the particular event (using their$on
method) do receive it, so the performance hit is not that big.Hi Michal,
I had the same idea of using child directives to move towards reusable components. Two questions about this:
1. When you have a parent and a child directive, the child directive can communicate with its parent directive via the controller. How does the parent directive inform its child directive when something changed? I tried it with scope.$broadcast(‘message’), but my child directive did not receive this event with scope.$on(‘message’, doSomething). Instead I had to use scope.$parent.$broadcast. How to solve this workaround?
2. Do you recommend to use child directives as own element like or as attribute . What would be the difference between both variants?
Thanks for your time.
Hi Daniel,
The simplest answer to your first question is to use Angular 1.3. In this version they’ve changed how trancluded scopes behave. Take a look at a sample I prepared on JSBin: http://jsbin.com/finitevitu/2/edit – if you change the Angular version, the events will be received correctly without the need to use $parent.
As for the second question – it’s really a matter of personal preference. I tend to use custom elements for reusable components and attributes for directives that alter the behavior of existing tags (as ng-show or ng-click do). So usually when a directive has a template, I’d use it as an element. However there’s nothing wrong with using attributes. And if you care about valid HTML, attributes are your only option.
Hope this helps.
Nice read. Found plenty of articles on ‘require:’ (like yours Part1), but very few on communication between siblings.
Strange that nobody mentions and looks like quite a few use ngModelController for communications. It’s not really communication channel, but in forms development it’s so handy to integrate with others JS libs (rich text editors, DT pickers, autocompleters…), to develop complex form blocks, or to hide difference in model structure and what’s needed in a template.
Strange that you skipped factory altogether as I find it very handy for some situations where random directives want to talk to a particular directive or set of directives and you want flexible placing or recipients. Factory can implement pub/sub model that can be quite isolated and controlled. Events can quickly go out of control.
Thanks for Post!
Hello Michal,
Can i call method in deep child directive with part 1 approach?