Rename require ngModel in Angular directive - angularjs

I have developed some directives and in them I use a certain pattern for naming attributes. All of the attributes are prefixed with "fs-" for example <div fs-attrtibute="value"></div>
Is it possible to use a custom name for the ngModel attribute and still retain the model controller functionality. For example <div fs-model="value" fsDirective></div>
myApp.directive('fsDirective', function() {
return {
require: 'fsModel',
link: function($scope, element, attrs, ngModel) {
ngModel.$setViewValue('StackOverflow');
};
}
};
});

As you've presented it here Angular would look for a controller on a directive named 'fsModel' and supply that to your link function's 'ngModel' argument. It might be possible to wrap ngModel in a custom 'pass-thru' directive, but that would introduce complexity for very small benefit.
We similarly use a 'namespace' for all of our local directives, but leave Angular's 'ng' space alone because it's clear and consistent.

Related

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>

Why do we "require" ngModel in a directive vs inject it?

Why do we "require" ngModel in a directive instead of in the directive injection arguments?
angular.module('customControl', ['ngSanitize']).
directive('contenteditable', ['$sce', function($sce) {
return {
restrict: 'A', // only activate on element attribute
require: '?ngModel',
Directives aren't injected. They are placed on elements which means that you need a specific instance in your custom directive.
requireing another directive on your own custom directive is how you tell Angular that to use your directive on an element, another directive is needed for things to work properly.

How do I reference the scope/model property of an element from within an AngularJS directive?

I am using a custom directive to attach the jQuery Chosen plugin to a multi-select element. (I realize there are similar native AngularJS plugins out there, but I want to learn how to integrate a jQuery plugin the right way because I'm sure I will eventually come across a requirement for which there exists only a jQuery plugin.)
The element is already bound to a scope model property, and in the directive, I attach a watch handler on this model property to ensure that the plugin's update/refresh function is called whenever it changes. However, I'm currently doing this by hard-coding the name of the model property into the directive, which is obviously not ideal. I want the directive to be able to figure that out on its own, so that it can be used in a variety of situations. I suppose I could pass it into the directive via an attribute value, but I would prefer if the directive was smart enough to figure it out on its own.
Is there a reference path available from the element object to its bound scope model property?
HTML:
<div ng-app="testApp" ng-controller="testController">
<select multiple ng-model="selection" jquery-ng-chosen>
<option value="1">First option</option>
<option value="2">Second option</option>
</select><br/>
<button type="button" ng-click="selection = []">Clear</button>
</div>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.4/angular.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/chosen/1.1.0/chosen.jquery.min.js"></script>
JS:
var testApp = angular.module('testApp', []);
testApp.controller('testController', function($scope) {
});
testApp.directive('jqueryNgChosen', function () {
return {
link: function (scope, element, attrs) {
element.chosen();
scope.$watch('selection', function () {
element.trigger("chosen:updated");
});
}
};
});
Fiddle. Notice that the clear button works properly because the directive has used a watch to update the jQuery chosen plugin. (Note: the jQuery JS and Chosen CSS links are omitted because they are defined from within the jsFiddle.)
Whenever you create a reusable directive like this you want to be sure to give it isolate scope (https://egghead.io/lessons/angularjs-understanding-isolate-scope)
In your isolate scope object you can bind the value of the ng-model attribute to your directive's scope with something like this: model: "=ngModel" Then you just have to watch the model property you put on your directives scope. You can choose to deep watch that property by passing true as the 3rd parameter to watch, although in this example you wouldn't need to
testApp.directive('jqueryNgChosen', function () {
return {
scope: {
'model': '=ngModel',
},
link: function (scope, element, attrs) {
element.chosen();
scope.$watch('model', function (newValue, old) {
element.trigger("chosen:updated");
}, true);
}
};
});
http://jsfiddle.net/z4vqxdtv/
To get the value in the ng-model attributes, you could use attrs.ngModel like this:
link: function (scope, element, attrs) {
element.chosen();
scope.$watch(attrs.ngModel, function () {
element.trigger("chosen:updated");
});
}
This way, you don't have to be worried if the attribute is actually be ng-model, data-ng-model, ng:model or other variant. It will be normalized by angular.
Hope this helps.

wrapping inputs in directives in angular

I had the idea to wrap inputs into custom directives to guarantee a consistent look and behavior through out my site. I also want to wrap bootstrap ui's datepicker and dropdown. Also, the directive should handle validation and display tooltips.
The HTML should look something like this:
<my-input required max-length='5' model='text' placeholder='text' name='text'/>
or
<my-datepicker required model='start' placeholder='start' name='start'/>
in the directives i want to create a dom structure like:
<div>
<div>..</div> //display validation in here
<div>..</div> //add button to toggle datepicker (or other stuff) in here
<div>..</div> //add input field in here
</div>
I tried various ways to achieve this but always came across some tradeoffs:
using transclude and replace to insert the input into the directives dom structure (in this case the directive would be restricted to 'A' not 'E' like in the example above). The problem here is, that there is no easy way to access the transcluded element as I want to add custom attributes in case of datepicker. I could use the transclude function and then recompile the template in the link function, but this seems a bit complex for this task. This also leads to problems with the transcluded scope and the toggle state for the datepicker (one is in the directives scope, the other in the transcluded scope).
using replace only. In this case, all attributes are applied to the outermost div (even if I generate the template dom structure in the compile function). If I use just the input as template, then the attributes are on the input, but I need to generate the template in the link function an then recompile it. As far as I understand the phase model of angular, I would like to avoid recompiling and changing the template dom in the link function (although I've seen many people doing this).
Currently I'm working with the second approach and generating the template in the link function, but I was wondering if someone had some better ideas!
Here's what I believe is the proper way to do this. Like the OP I wanted to be able to use an attribute directive to wrapper an input. But I also wanted it to work with ng-if and such without leaking any elements. As #jantimon pointed out, if you don't cleanup your wrapper elements they will linger after ng-if destroys the original element.
app.directive("checkboxWrapper", [function() {
return {
restrict: "A",
link: function(scope, element, attrs, ctrl, transclude) {
var wrapper = angular.element('<div class="wrapper">This input is wrappered</div>');
element.after(wrapper);
wrapper.prepend(element);
scope.$on("$destroy", function() {
wrapper.after(element);
wrapper.remove();
});
}
};
}
]);
And here's a plunker you can play with.
IMPORTANT: scope vs element $destroy. You must put your cleanup in scope.$on("$destroy") and not in element.on("$destroy") (which is what I was originally attempting). If you do it in the latter (element) then an "ngIf end" comment tag will get leaked. This is due to how Angular's ngIf goes about cleaning up its end comment tag when it does its falsey logic. By putting your directive's cleanup code in the scope $destroy you can put the DOM back like it was before you wrappered the input and so ng-if's cleanup code is happy. By the time element.on("$destroy") is called, it is too late in the ng-if falsey flow to unwrap the original element without causing a comment tag leak.
Why not doing a directive like that?
myApp.directive('wrapForm', function(){
return {
restrict: 'AC',
link: function(scope, inputElement, attributes){
var overallWrap = angular.element('<div />');
var validation = angular.element('<div />').appendTo(overallWrap);
var button = angular.element('<div />').appendTo(overallWrap);
var inputWrap = angular.element('<div />').appendTo(overallWrap);
overallWrap.insertBefore(inputElement);
inputElement.appendTo(inputWrap);
inputElement.on('keyup', function(){
if (inputElement.val()) {
validation.text('Just empty fields are valid!');
} else {
validation.text('');
}
});
}
}
});
Fiddle: http://jsfiddle.net/bZ6WL/
Basically you take the original input field (which is, by the way, also an angularjs directive) and build the wrappings seperately. In this example I simply build the DIVs manually. For more complex stuff, you could also use a template which get $compile(d) by angularjs.
The advantage using this class or html attribute "wrapForm": You may use the same directive for several form input types.
Why not wrap the input in the compile function?
The advantage is that you will not have to copy attributes and will not have to cleanup in the scope destroy function.
Notice that you have to remove the directive attribute though to prevent circular execution.
(http://jsfiddle.net/oscott9/8er3fu0r/)
angular.module('directives').directive('wrappedWithDiv', [
function() {
var definition = {
restrict: 'A',
compile: function(element, attrs) {
element.removeAttr("wrapped-with-div");
element.replaceWith("<div style='border:2px solid blue'>" +
element[0].outerHTML + "</div>")
}
}
return definition;
}
]);
Based on this: http://angular-tips.com/blog/2014/03/transclusion-and-scopes/
This directive does transclusion, but the transcluded stuff uses the parent scope, so all bindings work as if the transcluded content was in the original scope where the wrapper is used. This of course includes ng-model, also min/max and other validation directives/attributes. Should work for any content. I'm not using the ng-transclude directive because I'm manually cloning the elements and supplying the parent(controller's) scope to them. "my-transclude" is used instead of ng-transclude to specify where to insert the transcluded content.
Too bad ng-transclude does not have a setting to control the scoping. It would make all this clunkyness unnecessary.
And it looks like they won't fix it: https://github.com/angular/angular.js/issues/5489
controlsModule.directive('myWrapper', function () {
return {
restrict: 'E',
transclude: true,
scope: {
label: '#',
labelClass: '#',
hint: '#'
},
link: link,
template:
'<div class="form-group" title="{{hint}}"> \
<label class="{{labelClass}} control-label">{{label}}</label> \
<my-transclude></my-transclude> \
</div>'
};
function link(scope, iElement, iAttrs, ctrl, transclude) {
transclude(scope.$parent,
function (clone, scope) {
iElement.find("my-transclude").replaceWith(clone);
scope.$on("$destroy", function () {
clone.remove();
});
});
}
});

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