What does "require" of directive definition object take? - angularjs

require - Require another controller be passed into current directive
linking function. The require takes a name of the directive controller
to pass in. If no such controller can be found an error is raised. The name can be prefixed with:
? - Don't raise an error. This makes the require dependency optional.
^ - Look for the controller on parent elements as well.
Above is the definition from the official docs. The ambiguity here is what exactly is a "directive controller".
Take the tabs directive from the angularjs-ui bootstrap project, as an example.
angular.module('ui.bootstrap.tabs', [])
.controller('TabsController', ['$scope', '$element', function($scope, $element) {
... // omitted for simplicity
}])
.directive('tabs', function() {
return {
restrict: 'EA',
transclude: true,
scope: {},
controller: 'TabsController',
templateUrl: 'template/tabs/tabs.html',
replace: true
};
})
.directive('pane', ['$parse', function($parse) {
return {
require: '^tabs',
restrict: 'EA',
transclude: true,
scope:{
heading:'#'
},
link: function(scope, element, attrs, tabsCtrl) {
... // omitted for simplicity
},
templateUrl: 'template/tabs/pane.html',
replace: true
};
}]);
The pane directive has require: '^tabs', in which tabs is the name of a directive on its parent element, while the name of the controller attached to that directive is TabsController. From my own interpretation of the above definition, it should have been require: '^TabsController' not require: '^tabs' and that's obviously wrong. Please tell me what am I missing in my comprehension.

The require parameter, including its prefixes, is documented on the $compile API reference page.
Require another directive and inject its controller as the fourth argument to the linking function. The require takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the injected argument will be an array in corresponding order. If no such directive can be found, or if the directive does not have a controller, then an error is raised. The name can be prefixed with:
(no prefix) - Locate the required controller on the current element. Throw an error if not found.
? - Attempt to locate the required controller or pass null to the link fn if not found.
^ - Locate the required controller by searching the element and its parents. Throw an error if not found.
^^ - Locate the required controller by searching the element's parents. Throw an error if not found.
?^ - Attempt to locate the required controller by searching the element and its parents or pass
null to the link fn if not found.
?^^ - Attempt to locate the required controller by searching the element's parents, or pass
null to the link fn if not found.

This particular topic of the documentation is indeed confusing, however as strange as it seems to be it all makes sense.
The key to understand the logic behind this definition is to understand that "directive controller" refers to a directive's controller instance and not a controller factory.
Following the tabs example, when a tabs element is created, a new instance of the TabsController is also created and attached to that specific element data, something like:
tabElement.data('$tabsController', tabsControllerInstance)
The require: '^tabs' on the pane element is basically a request for that specific controller instance (tabsControllerInstance) being used on the parent tabs element.

Related

What is the difference between using "require:ngController" and "controller:" in a directive?

I'm switching from a directive created using:
return {
restrict: 'E',
templateUrl: '/src/templates/noise/swatches.html',
link: link,
controller: "swatchesController"
};
and
<swatches-directive></swatches-directive>
to using:
return {
restrict: 'E',
templateUrl: '/src/templates/noise/swatches.html',
link: link,
require: "ngController"
};
and
<swatches-directive ng-controller="swatchesController"></swatches-directive>
This seems to have unanticipated side-effects on existing watches belonging to other directives against scope variables that the swatches-directive assigns to. From what I understand, the new way introduces a new scope, so assigning watched variables to the parent scope seems like it should work, but those watches refuse to trigger.
Are there fundamental differences between the two methods used above?
Few points to note:
You should use the controllerAs property to segregate the
controllers. It's considered as a best practice. You can read it here
Ideally, you should provide the controller within the directive itself. The way you're doing it causes spagetti scopes. Keep the directive scope separate from parent scopes. If you want you can pass the required dependencies to a directive.
return {
restrict: 'E',
templateUrl: '/src/templates/noise/swatches.html',
link: link,
require: "ngDirective", //Get the controller of that directive
link : function(scope, element, attributes, ngController){
//With require, you say: get the controller of that directive. You can
//then use it as 4th parameter (ngController)
}
};
Note: you can pass multiple controllers
return {
restrict: 'E',
templateUrl: '/src/templates/noise/swatches.html',
link: link,
require: ["ngDirective1", "ngDirective2"], //Get the controller of that directive
link : function(scope, element, attributes, controllers){
var ctrl1 = controllers[0];
var ctrl2 = controllers[1];
//Require can be a string or, as this example, an array.
}
};
The directive passed inside require must be in your directive.
If it is not, you can say: find it on container elements with ^:
require : '^ngDirective'

AngularJS directive : require ngBind

I'm currently writing an attribute directive relying on the use of ngBind. I need the element to bear a ngBind attribute for the directive to work. I was thinking that a simple require: 'ngBind' would be enough, just like you'd do with ngModel. So here's what I did :
app.directive( 'myDirective', function() {
return {
restrict: 'A',
require: 'ngBind',
link: function(scope, element, attrs) { .. }
});
And here's how I use my directive:
<span my_directive="" ng_bind="valueToBeBound"></span>
But then I get this error, so I suppose it can't be done this way:
Error: error:ctreq
Missing Required Controller
Controller 'ngBind', required by directive 'myDirective', can't be found!
Is there any means to force the presence of ngBind ?
Thanks !
This is an expected behaviour. As defined in the AngularJS documentation for a directive's require option states that:
Require another directive and inject its controller as the fourth
argument to the linking function. The require takes a string name (or
array of strings) of the directive(s) to pass in. If an array is used,
the injected argument will be an array in corresponding order. If no
such directive can be found, or if the directive does not have a
controller, then an error is raised (unless no link function is
specified, in which case error checking is skipped).
Since the ngBind directive required by myDirective does not have a controller then an error is expected to be raised, unless you remove the link function in your myDirective directive then angular will simply skip the error checking.
There are two ways to achieve what you want.
Remove the link() function in your myDirective directive then add a controller function in that directive to add your component logic. The problem with this solution is that you can't attach DOM logic in your link() function.
The most ideal way to deal with the problem is to simply remove the require option and simply check the existence of the ngBind attribute in the element where your myDirective directive resides.
e.g.
app.directive( 'myDirective', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
if(angular.isUndefined(attrs.ngBind)) {
return;
}
// Your myDirective DOM LOGIC/MANIPULATION should be here
}
});
As explained well here: Angular NgModelController you need to provide the ngBind
<span ng-bind="ModelName"></span>

AngularJS : Child input directive needs to compile in the scope of its parent for ng-model to bind

We have a contact form we use in many applications. There are many default values, validation rules, structure, etc, that are repeated. We're working on a set of directives in order to make the view more semantic and less verbose.
There are a few targets we're shooting for.
Defining the contact form model once in a parent directive like this: <div my-form model='formModel'>. Associated children directives would be able to get the base model from the model attribute.
Supply the default configuration (size, validation rules, placeholders, classes, etc) for each input, but allow the possibility for attributes to be overwritten if necessary. Thus, we are creating child directives using the my-form directive's controller for communication. We also want these child directives to bind to the application controller's model formModel.
I'm having some trouble with implementing this.
formModel is exposed through the parent directive's controller, but I'm having to manually $compile the child directive using scope.$parent in the link function. This seems smelly to me, but if I try to use the child directive's scope the compiled HTML contains the correct attribute (it's visible in the source), but it isn't bound to the controller and it doesn't appear on any scope when inspected with Batarang. I'm guessing I'm adding the attribute too late, but not sure how to add the attribute earlier.
Although I could just use ng-model on each of the child directives, this is exactly what I'm trying to avoid. I want the resulting view to be very clean, and having to specify the model names on every field is repetitive and error-prone. How else can I solve this?
Here is a jsfiddle that has a working but "smelly" setup of what I'm trying to accomplish.
angular.module('myApp', []).controller('myCtrl', function ($scope) {
$scope.formModel = {
name: 'foo',
email: 'foo#foobar.net'
};
})
.directive('myForm', function () {
return {
replace: true,
transclude: true,
scope: true,
template: '<div ng-form novalidate><div ng-transclude></div></div>',
controller: function ($scope, $element, $attrs) {
$scope.model = $attrs.myModel;
this.getModel = function () {
return $scope.model;
};
}
};
})
.directive('myFormName', function ($compile) {
return {
require: '^myForm',
replace: true,
link: function (scope, element, attrs, parentCtrl) {
var modelName = [parentCtrl.getModel(),attrs.id].join('.'),
template = '<input ng-model="' + modelName + '">';
element.replaceWith($compile(template)(scope.$parent));
}
};
});
There is a much simpler solution.
Working Fiddle Here
Parent Form Directive
First, establish an isolated scope for the parent form directive and import the my-model attribute with 2-way binding. This can be done by specifying scope: { model:'=myModel'}. There really is no need to specify prototypical scope inheritance because your directives make no use of it.
Your isolated scope now has the 'model' binding imported, and we can use this fact to compile and link child directives against the parent scope. For this to work, we are going to expose a compile function from the parent directive, that the child directives can call.
.directive('myForm', function ($compile) {
return {
replace: true,
transclude: true,
scope: { model:'=myModel'},
template: '<div ng-form novalidate><div ng-transclude></div></div>',
controller: function ($scope, $element, $attrs) {
this.compile = function (element) {
$compile(element)($scope);
};
}
};
Child Field Directive
Now its time to setup your child directive. In the directive definition, use require:'^myForm' to specify that it must always reside within the parent form directive. In your compile function, add the ng-model="model.{id attribute}". There is no need to figure out the name of the model, because we already know what 'model' will resolve to in the parent scope. Finally, in your link function, just call the parent controller's compile function that you setup earlier.
.directive('myFormName', function () {
return {
require: '^myForm',
scope: false,
compile: function (element, attrs) {
element.attr('ng-model', 'model.' + attrs.id);
return function(scope, element, attrs, parentCtrl) {
parentCtrl.compile(element);
};
}
};
});
This solution is minimal with very little DOM manipulation. Also it preserves the original intent of compiling and linking input form fields against the parent scope, with as little intrusion as possible.
It turns out this question has been asked before (and clarified) here, but never answered.
The question was also asked on the AngularJS mailing list, where the question WAS answered, although the solution results in some smelly code.
Following is Daniel Tabuenca's response from the AngularJS mailing list changed a bit to solve this question.
.directive('foo', function($compile) {
return {
restrict: 'A',
priority: 9999,
terminal: true, //Pause Compilation to give us the opportunity to add our directives
link: function postLink (scope, el, attr, parentCtrl) {
// parentCtrl.getModel() returns the base model name in the parent
var model = [parentCtrl.getModel(), attr.id].join('.');
attr.$set('ngModel', model);
// Resume the compilation phase after setting ngModel
$compile(el, null /* transclude function */, 9999 /* maxPriority */)(scope);
}
};
});
Explanation:
First, the myForm controller is instantiated. This happens before any pre-linking, which makes it possible to expose myForm's variables to the myFormName directive.
Next, myFormName is set to the highest priority (9999) and the property of terminal is set true. The devdocs say:
If set to true then the current priority will be the last set of directives which will execute (any directives at the current priority will still execute as the order of execution on same priority is undefined).
By calling $compile again with the same priority (9999), we resume directive compilation for any directive of a lower priority level.
This use of $compile appears to be undocumented, so use at your own risk.
I'd really like a nicer pattern for follow for this problem. Please let me know if there's a more maintainable way to achieve this end result. Thanks!

Accessing parent directive's controller recursively in AngularJS

I need to get parent's controller, so my directive has a require property, as follows:
module.directive('tag', function () {
return {
require: '?^tag',
restrict: 'E',
controller: function () {
this.payload = getPayload();
},
link: function (scope, element, attrs, ctrl) {
usePayload(ctrl.payload);
}
};
});
However the ctrl parameter of the link function returns the controller of the current directive, not the parent's one. AngularJS documentation is clear about this:
?^ - Attempt to locate the required controller by searching the element's parents, or return null if not found.
What am I doing wrong?
Either the docs or the code are misleading here... require with ^ looks at the current element and then all parent, using the inheritedData method (see https://github.com/angular/angular.js/blob/master/src/ng/compile.js#L942). So you won't be able to require a directive with the same name from a parent using this approach.
When I've had this issue in the past I've looked at the form directive which needs to do what you are asking. Its controller method grabs the parent like so (https://github.com/angular/angular.js/blob/master/src/ng/directive/form.js#L39):
controller: function($element) {
var parentForm = $element.parent().controller('form');
}
Taking this, you should be able to call element.parent().controller('tag') to find the parent controller either in the controller or postLink methods.

Link vs compile vs controller

When you create a directive, you can put code into the compiler, the link function or the controller.
In the docs, they explain that:
compile and link function are used in different phases of the angular
cycle
controllers are shared between directives
However, for me it is not clear, which kind of code should go where.
E.g.: Can I create functions in compile and have them attached to the scope in link or only attach functions to the scope in the controller?
How are controllers shared between directives, if each directive can have its own controller? Are the controllers really shared or is it just the scope properties?
Compile :
This is the phase where Angular actually compiles your directive. This compile function is called just once for each references to the given directive. For example, say you are using the ng-repeat directive. ng-repeat will have to look up the element it is attached to, extract the html fragment that it is attached to and create a template function.
If you have used HandleBars, underscore templates or equivalent, its like compiling their templates to extract out a template function. To this template function you pass data and the return value of that function is the html with the data in the right places.
The compilation phase is that step in Angular which returns the template function. This template function in angular is called the linking function.
Linking phase :
The linking phase is where you attach the data ( $scope ) to the linking function and it should return you the linked html. Since the directive also specifies where this html goes or what it changes, it is already good to go. This is the function where you want to make changes to the linked html, i.e the html that already has the data attached to it. In angular if you write code in the linking function its generally the post-link function (by default). It is kind of a callback that gets called after the linking function has linked the data with the template.
Controller :
The controller is a place where you put in some directive specific logic. This logic can go into the linking function as well, but then you would have to put that logic on the scope to make it "shareable". The problem with that is that you would then be corrupting the scope with your directives stuff which is not really something that is expected.
So what is the alternative if two Directives want to talk to each other / co-operate with each other? Ofcourse you could put all that logic into a service and then make both these directives depend on that service but that just brings in one more dependency. The alternative is to provide a Controller for this scope ( usually isolate scope ? ) and then this controller is injected into another directive when that directive "requires" the other one. See tabs and panes on the first page of angularjs.org for an example.
I wanted to add also what the O'Reily AngularJS book by the Google Team has to say:
Controller - Create a controller which publishes an API for communicating across directives. A good example is Directive to Directive Communication
Link - Programmatically modify resulting DOM element instances, add event listeners, and set up data binding.
Compile - Programmatically modify the DOM template for features across copies of a directive, as when used in ng-repeat. Your compile function can also return link functions to modify the resulting element instances.
A directive allows you to extend the HTML vocabulary in a declarative fashion for building web components. The ng-app attribute is a directive, so is ng-controller and all of the ng- prefixed attributes. Directives can be attributes, tags or even class names, comments.
How directives are born (compilation and instantiation)
Compile: We’ll use the compile function to both manipulate the DOM before it’s rendered and return a link function (that will handle the linking for us). This also is the place to put any methods that need to be shared around with all of the instances of this directive.
link: We’ll use the link function to register all listeners on a specific DOM element (that’s cloned from the template) and set up our bindings to the page.
If set in the compile() function they would only have been set once (which is often what you want). If set in the link() function they would be set every time the HTML element is bound to data in the
object.
<div ng-repeat="i in [0,1,2]">
<simple>
<div>Inner content</div>
</simple>
</div>
app.directive("simple", function(){
return {
restrict: "EA",
transclude:true,
template:"<div>{{label}}<div ng-transclude></div></div>",
compile: function(element, attributes){
return {
pre: function(scope, element, attributes, controller, transcludeFn){
},
post: function(scope, element, attributes, controller, transcludeFn){
}
}
},
controller: function($scope){
}
};
});
Compile function returns the pre and post link function. In the pre link function we have the instance template and also the scope from the controller, but yet the template is not bound to scope and still don't have transcluded content.
Post link function is where post link is the last function to execute. Now the transclusion is complete, the template is linked to a scope, and the view will update with data bound values after the next digest cycle. The link option is just a shortcut to setting up a post-link function.
controller: The directive controller can be passed to another directive linking/compiling phase. It can be injected into other directices as a mean to use in inter-directive communication.
You have to specify the name of the directive to be required – It should be bound to same element or its parent. The name can be prefixed with:
? – Will not raise any error if a mentioned directive does not exist.
^ – Will look for the directive on parent elements, if not available on the same element.
Use square bracket [‘directive1′, ‘directive2′, ‘directive3′] to require multiple directives controller.
var app = angular.module('app', []);
app.controller('MainCtrl', function($scope, $element) {
});
app.directive('parentDirective', function() {
return {
restrict: 'E',
template: '<child-directive></child-directive>',
controller: function($scope, $element){
this.variable = "Hi Vinothbabu"
}
}
});
app.directive('childDirective', function() {
return {
restrict: 'E',
template: '<h1>I am child</h1>',
replace: true,
require: '^parentDirective',
link: function($scope, $element, attr, parentDirectCtrl){
//you now have access to parentDirectCtrl.variable
}
}
});
Also, a good reason to use a controller vs. link function (since they both have access to the scope, element, and attrs) is because you can pass in any available service or dependency into a controller (and in any order), whereas you cannot do that with the link function. Notice the different signatures:
controller: function($scope, $exceptionHandler, $attr, $element, $parse, $myOtherService, someCrazyDependency) {...
vs.
link: function(scope, element, attrs) {... //no services allowed
this is a good sample for understand directive phases
http://codepen.io/anon/pen/oXMdBQ?editors=101
var app = angular.module('myapp', [])
app.directive('slngStylePrelink', function() {
return {
scope: {
drctvName: '#'
},
controller: function($scope) {
console.log('controller for ', $scope.drctvName);
},
compile: function(element, attr) {
console.log("compile for ", attr.name)
return {
post: function($scope, element, attr) {
console.log('post link for ', attr.name)
},
pre: function($scope, element, attr) {
$scope.element = element;
console.log('pre link for ', attr.name)
// from angular.js 1.4.1
function ngStyleWatchAction(newStyles, oldStyles) {
if (oldStyles && (newStyles !== oldStyles)) {
forEach(oldStyles, function(val, style) {
element.css(style, '');
});
}
if (newStyles) element.css(newStyles);
}
$scope.$watch(attr.slngStylePrelink, ngStyleWatchAction, true);
// Run immediately, because the watcher's first run is async
ngStyleWatchAction($scope.$eval(attr.slngStylePrelink));
}
};
}
};
});
html
<body ng-app="myapp">
<div slng-style-prelink="{height:'500px'}" drctv-name='parent' style="border:1px solid" name="parent">
<div slng-style-prelink="{height:'50%'}" drctv-name='child' style="border:1px solid red" name='child'>
</div>
</div>
</body>
compile: used when we need to modify directive template, like add new expression, append another directive inside this directive
controller: used when we need to share/reuse $scope data
link: it is a function which used when we need to attach event handler or to manipulate DOM.

Resources