Reputation: 2799
Question in one sentence:
How do I, in a directive, access the controller of a child directive?
Longer description:
I'm writing a couple of directives to handle input from a remote controller (think TV controller). I am doing this because HTML does not have inherently good focus/cursor handling. My problem is that I am new to AngularJS and it feels like it is working against me.
In a particular view for example I want to be able to do something like this:
<div>
<my-linear-focus-container direction="horizontal">
<my-grid default-focused="true" style="...">{{gridItems}}</my-grid>
<my-button style="..."></my-button>
</my-linear-focus-container>
</div>
All views and "widgets" that wants to handle keys needs to have a FocusNode directive. The nodes will together create a focus tree and keys will propagate from the focused node in the tree down the branch to the root. When a new node is focused proper signaling will occur among the relevant tree nodes (lost focus, received focus, etc).
The linearFocusContainer's responsibility will be to switch focus between child widgets/directives. So if child A has focus (and does not listen to the right key) and the user presses right the linearFocusContainer will give focus to child B which lies right next to child A.
LinearFocusContainer directive
{
"restrict": "E",
"scope": {},
"template": "<div rs-focus-node keys='keys'></div>",
"link": function (scope) {
$scope.keyListeners = {
left: function () { /* focus child left of current focused */ },
right: function () { /* focus child right of current focused */ },
...
}
$scope.focusEventListeners = {
onFocusReceived: function () { /* focus to default child */ },
...
}
}
}
Heres my problem. For this to work I need access to the FocusNode directive "owned by" the LinearFocusContainer directive inorder to focus/access other children.
$scope.keyListeners = {
left: function () { focusNode.getChildren()[0].takeFocus(); }
}
That would also give me possibility to do:
focusNode.setKeyListeners({
...
});
And such instead of writing to a variable in the scope.
Upvotes: 1
Views: 321
Reputation: 40296
You cannot access the child controller directly, Angular does not support this.
What you can do is have the child require
the parent and pass the child controller to the parent as:
app.directive('parent', function() {
return {
...
controller: function() {
var childController;
this.setChildController = function(c) {
childController = c;
};
}
};
});
app.directive('child', function() {
return {
...
require: ['parent', 'child'],
controller: function() {
...
},
link: function(scope, elem, attrs, ctrls) {
ctrls[0].setChildController(ctrls[1]);
}
};
});
This demonstrates the principle, you can adjust it accordingly if there are more than one children.
Addressing the comment [...] the child does not know what directive the parent is. [...] Can require take "generic types"?
So no, require
cannot take generic types (and this would be a useful functionality, I've been running on it a lot lately). I can suggest 2 solutions:
Introduce an extra "coordinator directive". E.g for the case described in the comment, assume the following HTML:
<linear-focus-container focus-coordinator>
<my-grid default-focused="true" style="...">{{gridItems}}</my-grid>
</linear-focus-container>
Both the LinearFocusContainer
directive and the myGrid
will require the focusCoordinator
and cooperate through it. It could even implement some useful common functionality among the different possible types of parent directives.
(highly untested and probably DANGEROUS) All the parent directives put an object with a standard API to the DOM via angular.element.data()
under a well defined name. The child directives walk up the hierarchy of their DOM parents looking for this well defined name. At the very least do not forget to remove this object on $destroy
.
Upvotes: 2
Reputation: 3749
Why don't you use event broadcast/emit
mechanism provided by Angular JS.
$scope.$broadcast
will broadcast a event down all the child scopes. You can catch in a child scope using scope.$on
and similarly to notify parent scope for a change, you can use `$scope.$emit'.
From parent controller,
$scope.$broadcast('eventName', a_value_or_an_object);
And in the child controller,
scope.$on('eventName', function($event, value_or_object){});
By default, $emit
will cause event to be propagated towards $rootScope
, which means it will first hit parent, then parent's parent scope.
And if you want to cancel further propagation, you can use $event.preventDefault()
in the eventListener.
Upvotes: 0