Can I require a controller in a directive installed with ng controller?
I have (sort of) the following html:
<div ng-controller="MyController">
<my-sub-directive></my-sub-directive>
</div>
what the controller looks like, it doesn't matter:
app.controller("MyController", function($scope) {
$scope.foo = "bar";
})
and my directive looks like this:
function mySubDirective() {
return {
restrict: "E",
templateUrl:"aTemplate.html",
require: "^MyController",
link: function($scope, element) {
}
};
}
app.directive("mySubDirective", mySubDirective);
In the documentation, they always specify a different directive in require
-property, but it says that this means you need a controller. So I wanted to try this solution. However, I am getting the error
"The controller" MyController "required by the directive" mySubDirective "could not be found."
Is it not possible to request a controller from a directive if set by ng controller?
source to share
You can only do:
require: "^ngController"
So, you cannot be more specific than that, i.e. you cannot query "MainCtrl"
or "MyController"
by name, but it will get an instance of the controller:
.controller("SomeController", function(){
this.doSomething = function(){
//
};
})
.directive("foo", function(){
return {
require: "?^ngController",
link: function(scope, element, attrs, ctrl){
if (ctrl && ctrl.doSomething){
ctrl.doSomething();
}
}
}
});
<div ng-controller="SomeController">
<foo></foo>
</div>
I don't think this is a good approach, as it makes the directive very dependent on where it is used. You can follow the guidelines in the comments to pass the controller instance directly - this makes it somewhat more explicit:
<div ng-controller="SomeController as ctrl">
<foo ctrl="ctrl"></foo>
</div>
but it is still too general and can be easily used by users of your directive.
Instead, output a well-defined API (via attributes) and pass references to functions / properties defined in the controller:
<div ng-controller="SomeController as ctrl">
<foo do="ctrl.doSomething()"></foo>
</div>
source to share
You can use element.controller()
in the function links to check for the closest controller specified ngController
. The limitation of this method is that it doesn't tell you which controller it is. There are probably several ways to do this, but I prefer to call the controller constructor and expose it in scope, so you can useinstanceof
// Deliberately not adding to global scope
(function() {
var app = angular.module('my-app', []);
// Exposed in so can do "instanceof" in directive
function MyController($scope) {}
app.controller('MyController', MyController);
app.directive("foo", function(){
return {
link: function($scope, $element){
var controller = $element.controller();
// True or false depending on whether the closest
// ngController is a MyController
console.log(controller instanceof MyController);
}
};
})
})();
You can see this at http://plnkr.co/edit/AVmr7Eb7dQD70Mpmhpjm?p=preview
However, this won't work if you have nested ngController
s and want to test the one that isn't necessarily the closest. To do this, you can define a recursive function to navigate to the DOM tree:
app.directive("foo", function(){
function getAncestorController(element, controllerConstructor) {
var controller = element.controller();
if (controller instanceof controllerConstructor) {
return controller;
} else if (element.parent().length) {
return getAncestorController(element.parent(), controllerConstructor);
} else {
return void(0); // undefined
}
}
return {
link: function(scope, element){
var controller = getAncestorController(element, MyController);
// The ancestor controller instance, or undefined
console.log(controller);
}
};
})
You can see this at http://plnkr.co/edit/xM5or4skle62Y9UPKfwG?p=preview
For the docs reference, point out that the function controller
can be used to find controllers specified in ngController
:
By default gets the controller associated with the ngController directive
source to share