AngularJS: Retrieve Element Name from Directive - angularjs

I have spent some time looking for this but I haven't found anything.
I have the following
HTML file:
<my-directive name="someName" id="someId" method="somemethod">
sometext
</my-directive>
My directive:
app.directive('myDirective', function() {
return {
restrict: 'EA',
templateUrl: "example.html",
transclude: true,
link: function(scope, element, attrs)
{
alert(element.name); //Used for testing, Not working
}
};
});
I am trying to access the element parameters in the directive (name, method, id) but I am unable to figure out how.
Thanks in advance.

Please have a look at this Plnkr
You have the attrs as parameter inside the link function. Use that instead of the element.
link: function(scope, element, attrs) {
scope.result = attrs['name'];
}
You are also using transclusion, but you haven't defined a "ng-transclude" attribute in the template.

Using an alert for testing is very bad practice. You should be writing an assertion that specifically looks for the attribute you want (name in this case) and verifying that it is what you expect it to be. As commenter doodeec above said, you'll find the value you need under attrs.name. References to the element may also need to be element[0] to ensure that you do not get an undefined or null value. Lastly, you have your directive binding to both element and attribute, which seems to be a less than optimal situation. Were I you, I would bind to one or the other, but not both. It'll make for cleaner code in both places and remove some spaghetti.

Related

Validation isn't triggered though attributes are set

My directive looks as follows:
directive('setAttribute', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function ($scope, element, attrs, ctrl) {
var prop = $scope.$eval(attrs.setAttribute);
prop.validationRulesToApply.forEach(function (rule) {
attrs.$set(rule.name, rule.val);
});
}
}
});
As you can this one is for setting attributes dynamically. In spite of attributes are set properly(i can see them in final HTML) no validation is triggered. When i output $error object with curly braces - it is empty! Do i miss something important when setting attributes?
Are these validation rules you are trying to add in setAttribute directive?
If it so, you are doing in wrong way. When you use $set, it just adds attribute to HTMl, but doesnot compile them.
Hence, you won't get results as you are seeking.
You need to add it to pre compile
I think this solution may help you.
Add directives from directive in AngularJS

Passing a model to a custom directive - clearing a text input

What I'm trying to achieve is relatively simple, but I've been going round in circles with this for too long, and now it's time to seek help.
Basically, I have created a directive that is comprised of a text input and a link to clear it.
I pass in the id via an attribute which works in fine, but I cannot seem to work out how to pass the model in to clear it when the reset link is clicked.
Here is what I have so far:
In my view:
<text-input-with-reset input-id="the-relevant-id" input-model="the.relevant.model"/>
My directive:
app.directive('textInputWithReset', function() {
return {
restrict: 'AE',
replace: 'true',
template: '<div class="text-input-with-reset">' +
'<input ng-model="inputModel" id="input-id" type="text" class="form-control">' +
'<a href class="btn-reset"><span aria-hidden="true">×</span></a>' +
'</div>',
link: function(scope, elem, attrs) {
// set ID of input for clickable labels (works)
elem.find('input').attr('id', attrs.inputId);
// Reset model and clear text field (not working)
elem.find('a').bind('click', function() {
scope[attrs.inputModel] = '';
});
}
};
});
I'm obviously missing something fundamental - any help would be greatly appreciated.
You should call scope.$apply() after resetting inputModel in your function where you reset the value.
elem.find('a').bind('click', function() {
scope.inputModel = '';
scope.$apply();
});
Please, read about scope in AngularJS here.
$apply() is used to execute an expression in angular from outside of the angular framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). Because we are calling into the angular framework we need to perform proper scope life cycle of exception handling, executing watches.
I've also added declaring of your inputModel attribute in scope of your directive.
scope: {
inputModel: "="
}
See demo on plunker.
But if you can use ng-click in your template - use it, it's much better.
OK, I seem to have fixed it by making use of the directive scope and using ng-click in the template:
My view:
<text-input-with-reset input-id="the-relevant-id" input-model="the.relevant.model"/>
My directive:
app.directive('textInputWithReset', function() {
return {
restrict: 'AE',
replace: 'true',
scope: {
inputModel: '='
},
template: '<div class="text-input-with-reset">' +
'<input ng-model="inputModel" id="input-id" type="text" class="form-control">' +
'<a href ng-click="inputModel = \'\'" class="btn-reset"><span aria-hidden="true">×</span></a>' +
'</div>',
link: function(scope, elem, attrs) {
elem.find('input').attr('id', attrs.inputId);
};
});
It looks like you've already answered your question, but I'll leave my answer here for further explanations in case someone else lands on the same problem.
In its current state, there are two things wrong with your directive:
The click handler will trigger outside of Angular's digest cycle. Basically, even if you manage to clear the model's value, Angular won't know about it. You can wrap your logic in a scope.$apply() call to fix this, but it's not the correct solution in this case - keep reading.
Accessing the scope via scope[attrs.inputModel] would evaluate to something like scope['the.relevant.model']. Obviously, the name of your model is not literally the.relevant.model, as the dots typically imply nesting instead of being a literal part of the name. You need a different way of referencing the model.
You should use an isolate scope (see here and here) for a directive like this. Basically, you'd modify your directive to look like this:
app.directive('textInputWithReset', function() {
return {
restrict: 'AE',
replace: 'true',
template: [...],
// define an isolate scope for the directive, passing in these scope variables
scope: {
// scope.inputId = input-id attribute on directive
inputId: '=inputId',
// scope.inputModel = input-model attribute on directive
inputModel: '=inputModel'
},
link: function(scope, elem, attrs) {
// set ID of input for clickable labels (works)
elem.find('input').attr('id', scope.inputId);
// Reset model and clear text field (not working)
elem.find('a').bind('click', function() {
scope.inputModel = '';
});
}
};
});
Notice that when you define an isolate scope, the directive gets its own scope with the requested variables. This means that you can simply use scope.inputId and scope.inputModel within the directive, instead of trying to reference them in a roundabout way.
This is untested, but it should pretty much work (you'll need to use the scope.$apply() fix I mentioned before). You might want to test the inputId binding, as you might need to pass it a literal string now (e.g. put 'input-id' in the attribute to specify that it is a literal string, instead of input-id which would imply there is an input-id variable in the scope).
After you get your directive to work, let's try to make it work even more in "the Angular way." Now that you have an isolate scope in your directive, there is no need to implement custom logic in the link function. Whenever your link function has a .click() or a .attr(), there is probably a better way of writing it.
In this case, you can simplify your directive by using more built-in Angular logic instead of manually modifying the DOM in the link() function:
<div class="text-input-with-reset">
<input ng-model="inputModel" id="{{ inputId }}" type="text" class="form-control">
<span aria-hidden="true">×</span>
</div>
Now, all your link() function (or, better yet, your directive's controller) needs to do is define a reset() function on the scope. Everything else will automatically just work!

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>

simple directive to show static text

Our app is not using angular 1.3 (yet, we have to check the dependencies before updating), but I need to use One-time binding from 1.3 in some simple text attributes.
Wrote this directive to accomplish that
return {
scope: {
'text': '='
},
restrict: 'AE',
template: '{{ text }}',
link: function link($scope, element, attrs) {
}
};
And it is used like this
<span static-text text="friend.name">
The problem is that it still adds a watch on {{ text }} (screenshot from Batarang)
Is there a simple way of displaying a text without the permanent watch? (looked at this solution but seems to be too much just for showing some text).
EDIT: I ended up using the solutions proposed by #arturgrzesiak and #PSL, #arturgrzesiak's solution was used when no async proccesing was present, and for the other scenarios I used #PSL's. Both solutions work, but I'll accept #PSL's since it covers more scenarios.
There are some advantages that you get by having a watch. One example is in your actual code you are setting the data asynchronously which means the bound variable gets updated during the next digest cycle. But it's overkill (So bindonce or other watch removal libraries or 1.3 two-way binding exist) in some case. Here is one thing you can do, just use a watch until you get the data and then remove it once you have got it and set the html manually from the directive.
return {
restrict: 'AE',
link: function link($scope, element, attrs) {
var unwatch = $scope.$watch(attrs.staticText, function(val){ //Set up temp watch
if(val){
unwatch(); //Unwatch it
element.html(val); //Set the value
}
});
}
};
and just use it as
<span static-text="friend.name">
The solution is a bit more convoluted than what I proposed in the comment.
app.directive('once', function($parse){
return function(scope, element, attrs){
var parsed = $parse(attrs.once)(scope);
element.html(parsed);
}
});
DEMO

angularjs directive with dynamic templates and string interpolation

The idea is to replace the directive element with the dynamic template which refers to interpolated strings.
If I use element.html() in my directive then the strings are interpolated fine but this leaves the original custom directive html element.
If I use element.replaceWith() then strings are not interpolated. I guess it has related to scope but can't figure out what's wrong.
Plunker: http://plnkr.co/edit/HyBP9d?p=preview
UPDATE
Found the solution. Using element.replaceWith($compile(html)(scope)); works.
Updated plunker: http://plnkr.co/edit/HyBP9d?p=preview
Not sure what objective is regarding replaceWIth. The problem is likely that when you replace an elememnt, you replace all events and data bound to it. This would include the angular scope for the element.
For demo provided could do it like this:
app.directive('status', function($compile) {
var linker = function(scope, element, attrs) {
element.contents().wrap('<h'+attrs.value+'>')
};
return {
restrict: 'E',
replace: true,
template:'<div>{{value}}</div>',
transclude: true,
link: linker,
scope: {
value: '='
}
};
});
DEMO:http://plnkr.co/edit/QDxIwE?p=preview

Resources