TLDR: There are common ways to communicate between directives. But sometimes using a service to publish events can make things easier.
Overview
In the blog post, I will briefly describe three different ways to handle communication between directives.
- Passing information through the isolated scope
- Using $scope.$emit and $scope.$broadcast
- Using a pub/sub pattern in a service. See the live demo here.
The Most Common Ways
The most common way is to pass information through the isolated scope.
For example, if we wanted to choose a color from a selector and have it appear on a canvas, we could do something like the following.
<art-dashboard>
<art-canvas color="color"></art-canvas>
<art-color-selector color="color"></art-color-selector>
</art-dashboard>
The color is defined in the dashboard and passed to both the selector and the canvas.
The other common way is to pass information through $scope.$emit and $scope.$broadcast. Catching the events with $scope.on. In the above example, the selector could $emit up to the dashboard, and then the dashboard would $broadcast back down to the canvas.
<art-dashboard>
<art-canvas></art-canvas>
<art-color-selector></art-color-selector>
</art-dashboard>
Using A Service To Publish An Event The Directive Listens For
Sometimes the most common ways don’t fit the bill. Perhaps your directives are too far spread out. Perhaps you have an application with an enormous scope tree.
What I have found to be useful, is using a pub/sub design in a service. Create a service that publishes and subscribes events, which the directives can use directly.
The Pub/Sub Service – Handles subscribing to and publishing events.
(function() {
'use strict';
angular.module('app.art')
.service('ColorStateService', ColorStateService);
function ColorStateService () {
var subscriptionFn = null,
validColors = [
'primary',
'secondary',
'success',
'danger',
'warning',
'info',
'light',
'dark'
];
return {
getValidColors: getValidColors,
getDefaultColor: getDefaultColor,
subscribe: subscribe,
unsubscribe: unsubscribe,
publish: publish
};
function getValidColors () {
return validColors;
}
function getDefaultColor () {
return validColors[0];
}
function subscribe (fn) {
subscriptionFn = fn;
}
function unsubscribe () {
subscriptionFn = null;
}
function publish (args) {
subscriptionFn(args);
}
}
})();
The Color Selector Directive – Publishes an event whenever a color is selected.
(function() {
'use strict';
angular.module('app.art')
.directive('artColorSelector', artColorSelector);
function artColorSelector () {
return {
restrict: 'E',
scope: {},
controller: ctrl,
controllerAs: 'vm',
bindToController: true,
templateUrl: 'modules/art/directives/views/color-selector.html'
}
}
ctrl.$inject = ['ColorStateService'];
function ctrl (ColorStateService) {
var vm = this;
vm.colors = ColorStateService.getValidColors();
vm.selectedColor = ColorStateService.getDefaultColor();
vm.select = select;
function select (color) {
vm.selectedColor = color;
ColorStateService.publish(color);
}
}
})();
The Canvas Directive – Listens for color changes.
(function() {
'use strict';
angular.module('app.art')
.directive('artCanvas', artCanvas);
function artCanvas () {
return {
restrict: 'E',
scope: {},
controller: ctrl,
controllerAs: 'vm',
bindToController: true,
templateUrl: 'modules/art/directives/views/canvas.html'
}
}
ctrl.$inject = ['ColorStateService'];
function ctrl (ColorStateService) {
var vm = this;
vm.color = ColorStateService.getDefaultColor();
ColorStateService.subscribe(onColorChange);
function onColorChange (color) {
vm.color = color;
}
}
})();
Use Caution
If you use this design make sure you unsubscribe to all the events once you’re done with them. That way you don’t have any memory leaks. And your subscriber functions get garbage collected.
Also, you may want to have subscription topics, or modify the service to have an array of subscriptions. That way you can publish to specific or multiple subscribers.
Other Ways?
If you are aware of another good way to communicate between directives, please feel free to share in the comments below.