I'm rewording this question as I think the original wasn't too clear.
Basically, I have a 'wrapper' directive where I am attempting to dynamically add attributes to one of the wrapped (transcluded) elements. I can get this to work, but Angular doesn't seem to be aware of the new attributes once added.
If I use $compile
then Angular does recognise them - but at the expense of double-compiling the transcluded content, and in this case it then doubles the number of options
in the select
tag.
Here is a plunker that shows (with comments) what I am attempting, and the same code follows below for those who can look at code and suggest an answer just by looking:
(note - my ultimate aim is to check the custom directive valid-form-group
for the required
attribute, and if found to apply it to the contained select
tag)
HTML
<body ng-controller="MainCtrl">
<form name="validationForm" novalidate>
<valid-form-group class="form-group" ng-class="{'has-error': validationForm.validInfo.$error.required}" required>
<select ng-model="data.option" ng-options="option.id as option.message for option in selectOptions" name="validInfo" id="validInfo">
<option value="">-- Select a Question --</option>
</select>
</valid-form-group>
</form>
</body>
JS
var app = angular.module('plunker', [])
.controller('MainCtrl', function($scope) {
$scope.selectOptions = [
{id: 1, message: 'First option'},
{id: 2, message: 'Second option'},
{id: 3, message: 'Third option'}
];
})
.directive('validFormGroup', function($compile) {
return {
restrict: 'E',
template: '<div><span ng-transclude></span></div>',
replace: true,
transclude: true,
require: '^form',
link: function(scope, element, attrs, ctrl) {
if (attrs.required !== undefined) {
var selectElement = angular.element(element.find('select'));
// either of the below produce the same results
selectElement.attr('ng-required', true);
//selectElement.attr('required', true);
// if the below is commented out it wont validate
// BUT if it is left in it will recompile and add another 3 options
$compile(selectElement)(scope);
}
}
};
});
CSS
.has-error{
border: solid 1px red;
}
Please note that the sample here is using 'required
' (or ng-required
) as the added attribute to highlight the fact that Angular does not recognise it unless compiled.
Any help or comments are welcome - a bit disappointed that I can't get this to work, so perhaps there's something fundamental I'm missing...
The plunker should help with visualising my problem.
edit - apologies for the delay in responding to the answers and comments. As mentioned in a comment or two below, personal issues have prevented me from being able to find the time to investigate.
Try this simple directive:
.directive('validFormGroup', function($compile) {
return {
restrict: 'A',
replace: false,
require: '^form',
compile: function (element, attr) {
if (attr.required !== undefined) {
var selectElement = element.find('select');
// either of the below produce the same results
selectElement.attr('ng-required', true);
//selectElement.attr('required', true);
}
}
};
});
And use it as html attribute:
<div valid-form-group class="form-group" ng-class="{'has-error': validationForm.validInfo.$error.required}" required>
<select ng-model="data.option"
ng-options="option.id as option.message for option in selectOptions"
name="validInfo" id="validInfo" >
<option value="">-- Select a Question --</option>
</select>
<br/>
<br/>Required invalid? {{validationForm.validInfo.$error.required||false}}
<br/>
<br/>
</div>
DEMO
Explanation:
I don't use transclude
at all in this solution as the purpose of this directive is just to modify the html before compiling with the scope, there is no need for transcluded content which is overly complicated.
Here I handle the compile
function instead of the link
function. compile
function is a great place for you to modify html before linking to the scope.
I can only guess what you are seeing is the result of a double compile during the directives initialization process, that's why you are seeing the double sets of options.
You can solve this by wrapping your compile inside $timeout, which will ensure the compilation happens outside the directive initialization. Here's a working demo and below the directive code:
.directive('validFormGroup', function($compile, $timeout) {
return {
restrict: 'E',
template: '<div><span ng-transclude></span></div>',
replace: true,
transclude: true,
require: '^form',
link: function(scope, element, attrs, ctrl) {
if (attrs.required !== undefined) {
var selectElement = angular.element(element.find('select'));
$timeout(function(){
selectElement.attr('ng-required', true);
$compile(selectElement)(scope);
});
}
}
};
});
PS You can achieve similar bootstrap wrapper functionality by using isolated scope on your directive and then checking if your transcluded input/select element has the required attribute set. Define a function on the isolated scope to check for error and tie this function to the form-group ng-class has-error. This way you dont have to use $timeout at all.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With