Reputation: 6842
I have a custom dropdown directive that has common attributes such as class and ng-model.
I have decided to extend this control for support for validation and now need to include optional attributes that should only get included in the output template if they are set by the programmer.
Sample
I have a partially working system in which I moved my code out of a template URL and into a string concatenation which I call in the post: function of the directives compile.
I would have preferred to leave my directives HTML in a template, but could not get that working so I have this solution.
Questions:
Code for Directive
'use strict';
angular.module(APP)
.directive('wkKeyLabelSelect', ["$compile",
function($compile) {
return {
restrict: 'EA',
replace: true,
scope: {
'class': '@', // Permanent - One Way Attribute
ngModel: '=', // Permanent - Two Way Attribute (Angular)
items: '=', // Permanent - Two Way Attribute (Custom)
id: '@', // Dynamic - One Way Attribute
name: '@', // Dynamic - One Way Attribute
ngRequired: '=', // Dynamic - Two Way Attribute (Angular)
},
//templateUrl: COMPONENTS_PATH + '/keyLabelSelect/keyLabelSelect.html',
controller: 'KeyLabelSelectController',
link: function (scope, element, attrs) {
//$compile(element)(scope);
},
compile: function (element, attrs) {
// name & ngRequired are not available in the compile scope
//element.replaceWith($compile(html)(scope));
return {
pre: function preLink(scope, iElement, iAttrs, controller) {
},
post: function postLink(scope, iElement, iAttrs, controller) {
// Template goes here
var html =
'<select ' +
' class="{{class}}"' +
(scope.id ? ' id="{{id}}"' : "") +
(scope.name ? ' name="{{name}}"' : "") +
(scope.ngRequired ? ' ng-required="true"' : "") +
' ng-model="ngModel"' +
' ng-options="item.key as item.label for item in items"' +
'>' +
'</select>';
iElement.replaceWith($compile(html)(scope));
}
}
}
};
}
]);
Code for Directive Controller
angular.module(APP)
.controller('KeyLabelSelectController', ['$scope', function ($scope) {
$scope.klass = typeof $scope.klass === 'undefined' ? 'form-control' : $scope.klass;
console.log($scope.ngModel);
console.log($scope.items);
}]);
HTML used to run the directive
<div class="form-group" ng-class="{ 'has-error': editForm.state.$touched && editForm.name.$invalid }">
<label class="col-md-3 control-label">State</label>
<div class="col-md-9">
<wk-key-label-select id="state" name="state"
ng-required="true"
ng-model="model.entity.state"
class="form-control input-sm"
items="model.lookups.job_state">
</wk-key-label-select>
<div class="help-block" ng-messages="editForm.state.$error">
<p ng-message="required">Job State is required.</p>
</div>
</div>
</div>
My Original Template URL content, not used currently
<!-- This is now deprecated in place of inline string -->
<!-- How could I use a in place of string concatenation -->
<select class="{{klass}}"
name="{{name}}"
ng-model="ngModel"
ng-options="item.key as item.label for item in items"></select>
Upvotes: 4
Views: 3828
Reputation: 49590
The "proper" way to introduce a custom input controller is to support the ngModelController
. This enables your custom control to integrate with other directives that support ngModel
, like custom validators, parsers, <form>
s. This is a bit tricky, but makes your control indistinguishable from built-in controls for the framework:
.directive("customSelect", function() {
return {
require: "?ngModel",
scope: {
itemsExp: "&items" // avoids the extra $watcher of "="
},
template: '<select ng-model="inner" \
ng-options="item.key as item.label for item in itemsExp()"\
ng-change="onChange()"></select>',
link: function(scope, element, attrs, ngModel) {
if (!ngModel) return;
// invoked when model changes
ngModel.$render = function() {
scope.inner = ngModel.$modelValue;
};
scope.onChange = function() {
ngModel.$setViewValue(scope.inner);
};
}
};
});
Then, it can neatly integrate with other controls and leverage validators likes ng-required
natively:
<custom-select name="c1" ng-model="c1" items="items" ng-required="true">
</custom-select>
It may not seem like the answer to the question you asked, but that is only because your question is a bit of an XY question. By implementing a custom input control, you achieve what you set out to do - assign name
attribute to a directive (which registers itself with the form directive, if it is provided) and ng-required
works natively. However, if you must assign name
/id
to the underlying <select>
(for CSS reasons or whatnot), you could use ng-attr-
to conditionally apply an attribute. The template would change to:
<select ng-attr-name="attrs.name || undefined"
ng-attr-id ="attrs.id || undefined"
ng-model="inner" ...
Of course, you'd need to expose attrs
on the scope in the link function with:
link: function(scope, element, attrs, ngModel){
scope.attrs = attrs;
// etc...
}
Upvotes: 2