I have a directive with a require. Now I want to get the required controller instance AND the controller of the directive in the link function. How is that possible? If i set 'require' the fourth parameter of the link function only contains the required controller. If I do not set the requirement the fourth parameter of the link function contains the controller of the directive. How to get both?
You should require both, then the 4th argument will be an array of controllers (in the same order as the required directives.
E.g. from the source code of Angular's ngModel directive (which needs access to both NgModelController and its wrapping form's FormController):
var ngModelDirective = function() {
return {
require: ['ngModel', '^?form'],
controller: NgModelController,
link: function (scope, elem, attrs, ctrls) {
...
var modelCtrl = ctrls[0],
formCtrl = ctrls[1] || nullFormCtrl;
...
Related
So I was wondering what actually "required directive controller is not present on the current DOM element" means
here is the link of error:https://docs.angularjs.org/error/$compile/ctreq?p0=ngModel&p1=contenteditable
You are missing the ngModel directive for your custom directive.
Try something like this:
app.directive('contenteditable', function () {
return {
restrict: 'E',
require: 'ngModel',
link: function (scope) {
// do something
}
};
});
and in your HTML file add ng-model
<contenteditable ng-model="name"></contenteditable>
From the angular.js documentation on directives
When a directive uses require, $compile will throw an error unless the specified controller is found. The ^ prefix means that this directive searches for the controller on its parents (without the ^ prefix, the directive would look for the controller on just its own element).
I have a directive whose 'config' attribute value I need to access inside my directive controller.
Since the controller constructor get executed first,communication from controller to link is possible but not vice versa.
What should be the best way to achieve this?
I have considered the following approaches
1)Add the variable to scope-
That would in my opinion pollute the scope,making the variable accessible every where else where the scope is being shared.
2)Use $broadcast
Again the same issue as above
3) Pass a callback function on controller's this and call it from the link function with config as its argument
4)Pass the value through a service- In my case I have multiple such directives that would need to pass date through this service
Or is there some better approach that I am missing out for doing this?
module.directive('myDirective',function(){
return{
restrict:'E',
templateUrl:'path/to/html',
link:function(scope,iElement,iAttrs,controller){
var config=iAttrs.config;
//How to access this value inside the directive controller?
},
controller:function($scope){
//the directive attribute 'config' is required here for some larger computations which are not
//manipulating the DOM and hence should be seperated from the link function
})
There you can use isolated scope concept where you create isolated scope inside your controller & that would not be prototypically inherited from its parent scope. For that you need to use scope: { ... } inside your directive option. There are three options to pass scope value inside a directive through attribute
# : One way binding
= : Two way binding
& : Expression
In your case first two cases would be fine that are depends which one you need to use. If you just want to pass the value of scope variable to the directive in that case you could use 1st approach which would be # one way binding.
If you want to update the variable in both directive as well as controller from where it come i.e. nothing but two way binding, then you need to use =
I think = suits in your case so you should go for =
Markup
<my-directive config="config"></my-directive>
Directive
app.directive('myDirective', function() {
return {
restrict: 'E',
scope: {
config: '='
},
templateUrl: 'path/to/abc.html',
link: function(scope, iElement, iAttrs, controller) {
//here it will be access as scope.config
console.log(scope.config);
},
controller: function($scope) {
console.log($scope.config); //here also it would be available inisde scope
//you could put a watch to detect a changes on config
}
}
});
Demo Plunkr
Update
As config value has been provide from the attribute with expression like {{}} so we could get those changes inside controller by putting [**$observe**][2] on $attrs. For that you need to inject $attrs dependency on your controller that will give you all the attributes collection which are available on directive element. And on the same $attrs object we gonna put $observe which work same as that of $watch which does dirty checking & if value gets change it fires that watch.
Directive
app.directive('myDirective', function() {
return {
restrict: 'E',
templateUrl: 'path/to/abc.html',
link: function(scope, iElement, iAttrs, controller) {
//here it will be access as scope.config
console.log(scope.config);
},
controller: function($scope,$attrs) {
//you could put a watch to detect a changes on config
$attrs.$observe('config', function(newV){
console.log(newV);
})
}
}
});
Updated Demo
I have a pair of directives, one lives within the other and requires it using require: '^parentTag'. The two directives also declare their own controllers. Inside of the parent directive I can access its controller as the fourth argument to link: function(scope, el, attrs, ctrl) but in the child this fourth argument is either the required controller or an array of required controllers and the directives controller is no longer accessible.
I've tried to require the controller using the name used in controllerAs 'vm' and also by using the string name for the controller. Neither gets the controller back into that fourth argument.
The controller is accessible from the scope as scope.vm but I'd like to access it via that fourth argument if possible.
This Plunkr shows that the ChildController is not in the fourth argument and only on the scope but the ParentController is accessible as the fourth argument.
http://plnkr.co/edit/2WmB6Bqgc8MEG9JWz4wt
You could require the same directive by its name (never seen anyone actually do it):
function ChildTag() {
return {
restrict: 'AE',
require: ['^parentTag', 'childTag'],
scope: {},
template: '<div class="child-tag">Child</div>',
link: link,
controller: ChildController,
controllerAs: 'vm',
bindToController: true
}
function link(scope, element, attrs, ctrls, transclude) {
// ctrls[1] is ChildController
console.log('Child vm not found in ctrls', ctrls);
console.log('Child vm found on scope', scope.vm);
}
}
I see a link function in a directive in Angular like so:
link: function (scope, element, attrs, ctrls) {
var ngModelCtrl = ctrls[0],
invalidInputController = ctrls[1];
// ...
}
Can someone help me understand where the controllers in the ctrl parameter are coming from?
Edit: the directive has an angular require property specifying two other directives by name like so:
require: ['ngModel', '?numberFormatterPreventInvalidInput'],
I suspect they come from there.
Typically they come from the require part of a directive, which is either a string or an array of controllers.
['^something', '^another']
Within link they are accessed by ctrl[0] and ctrl[1].
Also from the documentation:
The basic difference (between controller and link) is that controller can expose an API, and link
functions can interact with controllers using require.
Best Practice: use controller when you want to expose an API to other
directives. Otherwise use link.
if in your directive is written
require:["ngModel","^directiveTwo"]
than ngModel , directiveTwo are names of directives and directiveTwo has to be a parent directive
link: function (scope, element, attrs, ctrls) {
var ngModelCtrl = ctrls[0],
controllerOfDirectiveTwo = ctrls[1];
// ...
}
ctrls the last parameter isthe array of the controller of the directives defined by require so for ngModel this :https://docs.angularjs.org/api/ng/type/ngModel.NgModelController for your directives defined by the controller: in your directive
I have the following directive
directiveModule.directive('typeahead', function() {
return {
restrict: 'E',
transclude: true,
scope: 'isolate',
templateUrl: 'assets/partials/common/typeahead.html' ,
controller: ["$scope","$element","$attrs","$transclude", "Event",
function ($scope,$element,$attrs,$transclude, Event){
console.log(eval($attrs.service + ".query();"));
$scope.search = function(){ console.log("Searching for:" + $scope.selectedValue) };
$scope.selectedValue = "";
$scope.providedItems = [];
}],
};
});
With the following template:
<div>
<input ng-change="search()" ng-model="selectedValue" ng-click="log()" autocomplete="off" type="text"/>
<ul class=".typeahead">
<li ng-repeat="item in providedItems">{{eval(item + "." + descriptor)}}</li>
</ul>
and the following call inside my view:
I would like to let the service attribute be evaluated at runtime when injecting to the controller in the directive. So that where it now says
controller: ["$scope","$element","$attrs","$transclude", "Event",
function ($scope,$element,$attrs,$transclude, Event){
It would say something similar to:
controller: ["$scope","$element","$attrs","$transclude", eval($attrs.service),
function ($scope,$element,$attrs,$transclude, eval($attrs.service)){
However I can't access the $attrs from the scope inside the directiveModule call.
If there is any way to access a service declared in the view that would suffice. Please help!
One solution for this, would be creating and binding the controller yourself. All you need is to inject both $injector (in order to resolve the service dynamically) and $controller (in order to resolve the controller dynamically). And in your linking function you create the controller yourself:
link: function(scope, elm, attr) {
$controller(function YourController($scope, dynamnicService) {
dynamicService.query();
}, {
$scope: scope,
dynamicService: $injector.get($attr.service)
}
)
}
There is one thing important here. I'm considering, in this example, that your service is a string in the element attribute. If it's a string inside the scope, referred by the attribute, then you have to change the approach. You should $attr.observe the attribute, and on change, you should grab the service $injector.get(...) and pass it to the controller. You ould either create a this.setService method on the controller itself, or $scope.setService method, your call. I'd rather the controller, as this is related to accessing a service. Using this second approach, you don't need to create the controller by hand, you can simple expose this method and set the data from outside.
One more info, you should NEVER TOUCH THE DOM FROM YOUR CONTROLLER. So passing $element, $attr and $transculde to the controller is probably a bad idea, no matter what.
Take a look at the docs about $controller, $injector and directive.