There are two main mechanisms by which data can flow between specific directives or controllers. Data can either flow down the scope hierarchy (which usually mirrors the DOM tree) using scopes and expressions, or it can flow up the hierarchy using directive controller APIs. Both of these mechanisms entail one directive communicating with one other specific directive.
A third communication mechanism is scope events. This mechanism is about one directive communicating with zero or more other directives/controllers which it doesn't necessarily know about.
Which mechanism to use depends on the specific scenario. The following sections give an overview of each, followed by a round-up of the trade-offs of each. (In the specific example you gave I'd use the first, but you seem to be interested in the general mechanisms and not just in your specific example.)
The idiomatic way to pass data down the tree is to provide the chart access to data from its parent scope. In that case, it would be used like this:
<div ng-controller="Foo">
<chart animated="chartAnimated"></chart>
</div>
The chartAnimated in the above is a scope variable inserted by the controller. Here's how that looks in the Foo controller:
.controller('Foo', function($scope) {
$scope.chartAnimated = true;
$scope.toggleAnimation = function () {
$scope.chartAnimated = ! $scope.chartAnimated;
};
});
The chart directive then needs to support this new attribute, which can be achieved using the scope property in the directive declaration:
.directive('chart', function() {
return {
scope: {
// This requests that Angular parse the expression in the 'animated'
// attribute and write a function for it into the scope as
// 'animationEnabled'.
'animationEnabled': '&animated'
},
link: function link(scope, iElement, attrs) {
// Now we can watch the expression to detect when it changes.
scope.$watch(
scope.animationEnabled,
function (isEnabled) {
// This function will be called once on instantiation and then
// again each time the value of the expression changes.
// Use ``isEnabled`` in here to either enable or disable animation.
console.log('Animation', isEnabled ? 'is enabled' : 'is disabled');
}
);
}
}
});
Although it's not really applicable to your given example, let's also explore the other data flow technique I mentioned, where data flows up the tree.
In this case, a parent directive can expose an API to a child directive. This is, for example, how the ngModel directive interacts with its parent form directive, or how ngSwitchWheninteracts with its parent ngSwitch.
The key here is the require property on the directive declaration, which allows a directive to depend on another directive either on the current element or on some parent element. For the sake of this example, we'll look for it on any parent element.
Let's make a contrived example of a parent directive with many children that it wants to keep track of for some reason:
<parent>
<child name="foo"></child>
<child name="bar"></child>
<child name="baz"></child>
</parent>
We'll define the parent directive first:
.directive('parent', function() {
return {
controller: function () {
this.children = {};
this.registerChild(name, child) {
console.log('Got registration for child', name);
this.children[name] = child;
}
}
}
});
The child directive is where we can make use of the require mechanism:
.directive('child', function() {
return {
require: '^parent', // Must be nested inside a 'parent' directive
link: function (scope, iElement, attrs, parentCtrl) {
// Notice the extra 'parentCtrl' parameter above.
// Provide an API for parent to interact with child.
var child = {};
child.doSomething = function () {
console.log('Child', attrs.name, 'requested to do something');
};
parentCtrl.registerChild(attrs.name, child);
}
}
});
In this case we establish a bidirectional communication channel between the parent and the child, with the child initiating the channel using require, and passing to the parent an object through which it can communicate with the child. When require is used there is an extra argument to link giving the controller of the directive that was requested.
Finally, let's talk about events. These are best applied in a situation where you have a single directive (or indeed, any other code that owns a scope) that wishes to broadcast a particular notification to whoever is listening. For example, $route communicates with ng-view (and anyone else who is listening) using the $routeChangeSuccess event, and ng-view alerts the rest of the application that the view is ready via $viewContentLoaded.
Event watchers belong to scopes, and events propagate up and down the scope hierarchy.
If you're holding a scope, you can watch for any events that might pass by using scope.$on:
scope.$on(
'$viewContentLoaded',
function () {
console.log('view content loaded!');
}
);
If you want to send an event, you can either send a message up the scope hierarchy using $emit:
scope.$emit(
'somethingHappened'
);
...or you can send a message down the scope hierarchy using $broadcast:
scope.$broadcast(
'somethingHappened'
);
In some circumstances you wish to pass an event to the entire application, in which case you can $broadcast on the $rootScope:
$rootScope.$broadcast(
'somethingHappened'
);
One important thing to keep in mind with events is that they are a "point-to-multipoint" mechanism, which is to say that many different recipients may "see" the same message. This makes events a rather poor mechanism for directed communication between two specific participants, and so events should be used sparingly.
So there's an overview of three data-flow mechanisms for directives in AngularJS. There are different trade-offs for each:
- Passing data to child directives via scope keeps the child directive decoupled from the parent, but requires the parent directive (or, in your case, controller) to provide the data that the child needs.
- The
require mechanism is best used to provide template constructs that require the participation of multiple strongly-related elements, such as in the case of ngSwitch where ngSwitchWhen exists only to be used with ngSwitch, and is of no use without it.
- Events are best used for broad notifications, where the sender doesn't especially care who receives the message, and the recipient doesn't necessarily know who sent it. In this case, sender and recipient are truly decoupled from one another, and there may not even be a recipient.