My directive "addOnce" is required to check if it has an "restrict" attribute and if yes, then attach certain other directives to the input box which is one of it's sub-children.
I have been able to layout a framework to get this done. However getting stuck at how I can dynamically attach other directives to the input box.
Plnkr here: http://plnkr.co/edit/jYiTtTQ0uPuH40zcMcer
app.directive('addOnce', ['$timeout', function($timeout){
return {
restrict: 'E',
link: function(scope, el, attrs){
if(attrs.restrict){
var input = el.find('input');
$timeout(function(){
input.val('testing');
}, 50)
// now attach directive restrict-symbols to the input box
// as a result the html would look like
//<input type="text" class="aClass" ng-model="aModel" restrict-symbols>
}
}
};
}]);
Any clues will be appreciated.
The necessary part for nested directives would be:
input.attr('restrict-symbols', '');
$compiled = $compile(input)(scope);
Take a look at the modified plunkr for the full example: http://plnkr.co/edit/HCTGj1ivp48cUXBXpn1Q?p=preview
What I did was simply to add the needed attribute to the input field, afterwards compile the element, so the nested directive gets executed. The code of the second directive is not very sophisticated but enough to demonstrate that it works
Related
I am trying to create a "sticky table header" component for which I need to copy parts of the transcluded content of my directive.
Depending on how I transclude the content, it works only partially at best: with $compile, expressions are updated when the underlying data changes, but ng-repeat does not seem to work at all. It does not even render the first time, let alone update later. Simply appending the partial content I found does not seem to work at all: element.append($(transcludedEl).find('.wrapper'));
To illustrate my point, I have created a plunkr using three versions of the same code: http://plnkr.co/edit/xkAkzl8ID3m5Ras3Ww31
The first is super-simple direct ng-repeat, which only serves to show what should happen.
The second uses a directive that transcludes its full content, which works but is not what I need.
The third (reproduced below) uses a directive to try and include only part of its content, which is what I need, but which does not work.
The interesting bit is this:
app.directive('stickyPartial', ['$compile', '$timeout', function($compile, $timeout) {
return {
restrict: 'E',
transclude: true,
template: '<div></div>',
link: function(scope, element, attrs, controller, transclude) {
transclude(scope, function(transcludedEl) {
// this is what i want to achieve - not working
// element.append($(transcludedEl).find('.wrapper'));
// neither is this, though it does support expressions
$compile($(transcludedEl).find('.wrapper'))(scope, function(clone) {
element.append(clone);
});
});
}
};
}]);
So far, I have tried several combinations of $compile, .clone() and .html(), but to no avail. I can neither get a working partial DOM tree from the compiled template, nor a useful partial HTML source with ng-repeat intact that I can then compile manually.
As a last resort, I might try copying the DOM after angular is done (which seemed to work, previously) and then manually repeat this process every time the relevant model data changes. If there is another way, thought, I'd very much like to avoid this.
Using https://stackoverflow.com/a/24729270/2029017 I found a solution with compile that does what I want: http://plnkr.co/edit/V4yUbiAD9EAaaJXihziv
I've created a custom directive to create HTML elements dynamically. As part of this I'm passing JSON values dynamically to my custom directive to create attributes for the HTML element inside my custom directive. Here, I'm trying to pass a string value to be bound to the ng-model but it's considering the dynamic JSON value as a string literal instead of a property to hold the input value.
Another interesting point is, it is working fine if I pass the property name as a hard coded value to the custom directive. But I want to pass a dynamic value to be treated as the property name to be bound to the ng-model. Could you please help me on this?
Here is my code and it's not working. Not sure what's wrong. Please suggest me.
Inside my HTML file, below is what I'm using:
Inside my custom directive, I've below snippets:
app.directive('myCustomDirective', ['$compile', '$parse', function ( $compile, $parse) {
return {
restrict:'E',
replace : true,
require: '?ngModel',
scope: {
dynamicmodel :'=dynamicmodel'
},
template : '<input type="text" ng-model="dynamicmodel" />',
link : function(scope, element, attributes, frmCntrl) {
}
}
}]);
You are making several mistakes here. First of all the array does not match the function parameters of your directive:
['$compile', '$parse', function ($rootScope, $compile, $parse)
I don't see why you need any of these but they at least have to match. For example without $rootScope:
['$compile', '$parse', function ($compile, $parse)
Second the template is syntactically incorrect because you use " and you are missing a :. This is even visible here because the highlighting does not work on your lines starting with template.
Then you do not seem to use the name of your directive mycustomDirective at all.
My advice would be to check your console and correct all errors that are shown and check back with what you have then. What you posted has too many errors.
I have a custom directive that generates an input with its validation errors , eventually after building the input, here is the part I'm wondering about :
var html= template.get();
element.html(html);
$compile(element)(scope);
I also added to the directive object which I think is not making difference since I don't have template in the object or should it?
replace:true
but yet the directive element DOM is still there , and the generated input is being appended as a child , can you help me out in this ?
Yes, replace is used in conjunction with template/templateUrl.
For the templates that are retrieved dynamically in link, use
var html= template.get();
element.replaceWith($compile(html)(scope));
Notice that obvious drawback in comparison with replace is that directive's attributes won't be translated to template element, this has to be done manually.
Its not working because you haven't provided a template parameter and your link function was manually adding the elements. Remove the link function and add a template parameter like so and it'll work. Ive updated your fiddle with it working too
app.directive('test',function($compile){
return {
restrict:'E',
replace:true,
template:'<input type="text" name="test">'
}
});
I have a question about the way Angular bootstraps. Consider this directive:
app.directive('finder', function () {
return {
restrict: 'E',
replace: true,
template:'<div><input type="text" data-ng-model="my-input" style="width:80%;" /></div>',
compile: function (element, attributes) {
if (attributes.hasOwnProperty('required')) {
element.removeAttr('required');
element.find(':input').attr('required', true);
element.removeClass("ng-invalid ng-invalid-required");
}
return {
post: function postLink(scope, iElement, attributes) {
// Do some other stuff
}
}
}
}
})
And I use this directive as follows:
<div>
<finder required></finder>
</div>
Some CSS:
.ng-invalid-required {
background: yellow;
}
As you can see in the directive, in the compile phase, I remove the required attribute from the element and add the attribute to the input element. The result is what you (sort of) should expect: the main element doesn't have the attribute anymore, but the input field does.
Now the 'strange' part: The output shows that the div also has a yellow background. As it turns out, the main element (div) has the ng-invalid-required class! This is strange because I removed the required attribute in the compile phase...
How is this possible?
Apparently, Angular scans the whole DOM for directives and collect the directives with the belonging elements. After the scan, Angular is going to compile all the directives. At one point it is going to compile my finder directive. This directive removes the required attribute and add it to my input field. Later on it compiles the required attribute. Because the 'original element' is in the collected list it is going to add the ng-invalid-required class to the linked element (the div).
Is this correct? Or is there another explanation for this behaviour?
EDIT (thanks to Nikos Paraskevopoulos suggestion in his comment).
Interesting: When I add this to the directive:
priority: 1000,
terminal: true
It works like I would expect. But when I leave one of those properties, it doesn't work anymore. Could someone explain to me why this works?
(Moved from comment to answer + explanation)
Try specifying both priority (to a value so that your directive runs before the requiredDirective) and terminal: true in your directive definition.
Explanation:
The reason for the original problem is that the requiredDirective executes on the element. Obviously, as you point out, Angular scans the whole DOM for directives and collects the directives and executes them. To clarify: first it collects them, then executes them.
This means that the requiredDirective will execute despite your compile() function having removed the required attribute, because Angular has already "collected" the directives.
The terminal: true property:
If set to true then the current priority will be the last set of directives which will execute
This means that if you set priority to a value so that your directive executes before the requiredDirective, the latter will not execute at all! Obviously, Angular stops the collection of directives when it encounters one with terminal: true.
"required" is compiled before your element, so I think that even if you remove required attribute it was already marked as required and angularjs adds ng-invalid class.
Try to change the priority of your directive to 1000, so it will be compiled before required directive and so you can remove required attribute:
app.directive('finder', function () {
return {
priority: 100,
restrict: 'E',
...
};
});
I try to create a directive which should peform some actions when an input field is marked as invalid. For this example lets assume I have a directive which checks if the input is a prime number, and I want to create a directive which adds a class to the element when it's invalid:
<input type="text" ng-model="primeNumber" validate-prime invalid-add-class="error">
The validate-prime uses the parsers and formatters on ng-model to update the validity of the model.
Now I want the invalid-add-class directive to add the class "error" when the model is invalid, and to remove it when it is valid. In other words, it should watch the $valid (or $invalid) property of the model controller. However, I can't figure out how to get this working. I tried:
link : function(scope, element, attrs, ctrl) {
ctrl.$watch("$valid", function(newVal, oldVal) {
//never fired
});
}
Perhaps I could watch some variable on scope, but I don't know which variable to watch for.
So how can I be notified when the validity of a model changes?
If you have a <form>, add a name to it (lets assume 'myForm') and a name to your input (lets assume myInput). You should be able to $watch this by:
scope.$watch('myForm.myInput.$valid', function(validity) {})
If you don't have a form, you can always watch a function. This way:
scope.$watch(function() { return ctrl.$valid; }, function(validity){});
You can read more about the form approach here.
If you do not have a <form />you can easily get one:
In your directive definition:
require: '^form'
and then in your link function, the form is passed as the fourth parameter:
link: function (scope, element, attr, ctrl) {
Now you don't have to hard-code the form or the input field to perform the $watch:
scope.$watch(ctrl.$name + '.' + element.attr('name') + '.$valid',
function (validity) {});
Our goal, in general, should be to make a directive work independently of any one form or input. How can we allow it to read the local $valid property without imperatively binding it to a single specific form & input name?
Just use require: 'ngModel' as one of the properties of your directive config. This will inject the local ngModel controller as the fourth argument to the link function, and you can place a $watch directly upon $valid without needing to couple the directive's implementation to any particular form or input.
require: 'ngModel',
link: function postLink(scope, element, attrs, controller) {
scope.inputCtrl = controller;
scope.$watch('inputCtrl.$valid', handlerFunc)
}
The handler should consistently fire on changes to $valid with that structure. See this Fiddle, where the input is validated for the pattern of a U.S. Zip-Code or Zip+4. You'll get an alert each time validity changes.
EDIT 3/21/14: This post previously got hung up on a delusion of mine, fixating on the wrong cause of an implementation problem. My fault. The example above removes that fixation. Also, added the fiddle, showing that this approach does in fact work, and always did, once you add quotes around the watch expression.