在指令中自定义模板

我有一个使用 Bootstrap 标记的表单,如下所示:

<form class="form-horizontal">
<fieldset>
<legend>Legend text</legend>
<div class="control-group">
<label class="control-label" for="nameInput">Name</label>
<div class="controls">
<input type="text" class="input-xlarge" id="nameInput">
<p class="help-block">Supporting help text</p>
</div>
</div>
</fieldset>
</form>

里面有很多样板代码,我想把它们简化成一个新的指令表单输入,如下所示:

<form-input label="Name" form-id="nameInput"></form-input>

产生:

   <div class="control-group">
<label class="control-label" for="nameInput">Name</label>
<div class="controls">
<input type="text" class="input-xlarge" id="nameInput">
</div>
</div>

我有这么多的工作通过一个简单的模板。

angular.module('formComponents', [])
.directive('formInput', function() {
return {
restrict: 'E',
scope: {
label: 'bind',
formId: 'bind'
},
template:   '<div class="control-group">' +
'<label class="control-label" for="{{formId}}">{{label}}</label>' +
'<div class="controls">' +
'<input type="text" class="input-xlarge" id="{{formId}}" name="{{formId}}">' +
'</div>' +
'</div>'


}
})

然而,当我开始添加更高级的功能时,我却陷入了困境。

如何支持模板中的默认值?

我想在我的指令中将“ type”参数作为可选属性公开,例如:

<form-input label="Password" form-id="password" type="password"/></form-input>
<form-input label="Email address" form-id="emailAddress" type="email" /></form-input>

但是,如果没有指定任何内容,我希望默认为 "text"。我如何支持它?

我如何根据是否存在属性来定制模板?

我还希望能够支持“必需”属性(如果它存在的话)。 例如:

<form-input label="Email address" form-id="emailAddress" type="email" required/></form-input>

如果指令中存在 required,我希望将它添加到输出中生成的 <input />中,否则忽略它。我不知道该怎么做。

我怀疑这些需求可能已经超越了简单的模板,必须开始使用预编译阶段,但我不知道从哪里开始。

105543 次浏览

Here's what I ended up using.

I'm very new to AngularJS, so would love to see better / alternative solutions.

angular.module('formComponents', [])
.directive('formInput', function() {
return {
restrict: 'E',
scope: {},
link: function(scope, element, attrs)
{
var type = attrs.type || 'text';
var required = attrs.hasOwnProperty('required') ? "required='required'" : "";
var htmlText = '<div class="control-group">' +
'<label class="control-label" for="' + attrs.formId + '">' + attrs.label + '</label>' +
'<div class="controls">' +
'<input type="' + type + '" class="input-xlarge" id="' + attrs.formId + '" name="' + attrs.formId + '" ' + required + '>' +
'</div>' +
'</div>';
element.html(htmlText);
}
}
})

Example usage:

<form-input label="Application Name" form-id="appName" required/></form-input>
<form-input type="email" label="Email address" form-id="emailAddress" required/></form-input>
<form-input type="password" label="Password" form-id="password" /></form-input>
angular.module('formComponents', [])
.directive('formInput', function() {
return {
restrict: 'E',
compile: function(element, attrs) {
var type = attrs.type || 'text';
var required = attrs.hasOwnProperty('required') ? "required='required'" : "";
var htmlText = '<div class="control-group">' +
'<label class="control-label" for="' + attrs.formId + '">' + attrs.label + '</label>' +
'<div class="controls">' +
'<input type="' + type + '" class="input-xlarge" id="' + attrs.formId + '" name="' + attrs.formId + '" ' + required + '>' +
'</div>' +
'</div>';
element.replaceWith(htmlText);
}
};
})

The above answers unfortunately don't quite work. In particular, the compile stage does not have access to scope, so you can't customize the field based on dynamic attributes. Using the linking stage seems to offer the most flexibility (in terms of asynchronously creating dom, etc.) The below approach addresses that:

<!-- Usage: -->
<form>
<form-field ng-model="formModel[field.attr]" field="field" ng-repeat="field in fields">
</form>
// directive
angular.module('app')
.directive('formField', function($compile, $parse) {
return {
restrict: 'E',
compile: function(element, attrs) {
var fieldGetter = $parse(attrs.field);


return function (scope, element, attrs) {
var template, field, id;
field = fieldGetter(scope);
template = '..your dom structure here...'
element.replaceWith($compile(template)(scope));
}
}
}
})

I've created a gist with more complete code and a writeup of the approach.

Tried to use the solution proposed by Misko, but in my situation, some attributes, which needed to be merged into my template html, were themselves directives.

Unfortunately, not all of the directives referenced by the resulting template did work correctly. I did not have enough time to dive into angular code and find out the root cause, but found a workaround, which could potentially be helpful.

The solution was to move the code, which creates the template html, from compile to a template function. Example based on code from above:

    angular.module('formComponents', [])
.directive('formInput', function() {
return {
restrict: 'E',
template: function(element, attrs) {
var type = attrs.type || 'text';
var required = attrs.hasOwnProperty('required') ? "required='required'" : "";
var htmlText = '<div class="control-group">' +
'<label class="control-label" for="' + attrs.formId + '">' + attrs.label + '</label>' +
'<div class="controls">' +
'<input type="' + type + '" class="input-xlarge" id="' + attrs.formId + '" name="' + attrs.formId + '" ' + required + '>' +
'</div>' +
'</div>';
return htmlText;
}
compile: function(element, attrs)
{
//do whatever else is necessary
}
}
})