使用 AngularJS 在表单中进行动态验证和命名

我有这张表: http://jsfiddle.net/dfJeN/

正如您所看到的,输入的名称值是静态设置的:

name="username"

表单验证工作正常(添加一些内容并从输入中删除所有文本,必须显示文本)。

然后尝试动态设置名称值: http://jsfiddle.net/jNWB8/

name="{input.name}"

然后我将这个应用到我的验证中

login.{{input.name}}.$error.required

(这个模式将在 ng-repeat 中使用)但是我的表单验证失败了。它在我的浏览器中被正确解释(如果我检查我看到的 login.username 元素的话。$error.須要求)。

知道吗?

编辑: 在控制台中记录范围之后,似乎

{{input.name}}

我的表单作为{{ input.name }}属性但没有用户名。

更新: 因为1.3.0-rc. 3 name = “{{ input.name }}”工作正常。 请参见 < a href = “ https://github.com/angle/angular.js/questions/1404”rel = “ norefrer”> # 1404

100150 次浏览

You can't do what you're trying to do that way.

Assuming what you're trying to do is you need to dynamically add elements to a form, with something like an ng-repeat, you need to use nested ng-form to allow validation of those individual items:

<form name="outerForm">
<div ng-repeat="item in items">
<ng-form name="innerForm">
<input type="text" name="foo" ng-model="item.foo" />
<span ng-show="innerForm.foo.$error.required">required</span>
</ng-form>
</div>
<input type="submit" ng-disabled="outerForm.$invalid" />
</form>

Sadly, it's just not a well-documented feature of Angular.

Using nested ngForm allows you to access the specific InputController from within the HTML template. However, if you wish to access it from another controller it does not help.

e.g.

<script>
function OuterController($scope) {
$scope.inputName = 'dynamicName';


$scope.doStuff = function() {
console.log($scope.formName.dynamicName); // undefined
console.log($scope.formName.staticName); // InputController
}
}
</script>


<div controller='OuterController'>
<form name='myForm'>
<input name='\{\{ inputName }}' />
<input name='staticName' />
</form>
<a ng-click='doStuff()'>Click</a>
</div>

I use this directive to help solve the problem:

angular.module('test').directive('dynamicName', function($compile, $parse) {
return {
restrict: 'A',
terminal: true,
priority: 100000,
link: function(scope, elem) {
var name = $parse(elem.attr('dynamic-name'))(scope);
// $interpolate() will support things like 'skill'+skill.id where parse will not
elem.removeAttr('dynamic-name');
elem.attr('name', name);
$compile(elem)(scope);
}
};
});

Now you use dynamic names wherever is needed just the 'dynamic-name' attribute instead of the 'name' attribute.

e.g.

<script>
function OuterController($scope) {
$scope.inputName = 'dynamicName';


$scope.doStuff = function() {
console.log($scope.formName.dynamicName); // InputController
console.log($scope.formName.staticName); // InputController
}
}
</script>


<div controller='OuterController'>
<form name='myForm'>
<input dynamic-name='inputName' />
<input name='staticName' />
</form>
<a ng-click='doStuff()'>Click</a>
</div>

Just a little improvement over EnlSeek solution

angular.module('test').directive('dynamicName', ["$parse", function($parse) {
return {
restrict: 'A',
priority: 10000,
controller : ["$scope", "$element", "$attrs",
function($scope, $element, $attrs){
var name = $parse($attrs.dynamicName)($scope);
delete($attrs['dynamicName']);
$element.removeAttr('data-dynamic-name');
$element.removeAttr('dynamic-name');
$attrs.$set("name", name);
}]


};
}]);

Here is a plunker trial. Here is detailed explantion

The problem should be fixed in AngularJS 1.3, according to this discussion on Github.

Meanwhile, here's a temporary solution created by @caitp and @Thinkscape:

// Workaround for bug #1404
// https://github.com/angular/angular.js/issues/1404
// Source: http://plnkr.co/edit/hSMzWC?p=preview
app.config(['$provide', function($provide) {
$provide.decorator('ngModelDirective', function($delegate) {
var ngModel = $delegate[0], controller = ngModel.controller;
ngModel.controller = ['$scope', '$element', '$attrs', '$injector', function(scope, element, attrs, $injector) {
var $interpolate = $injector.get('$interpolate');
attrs.$set('name', $interpolate(attrs.name || '')(scope));
$injector.invoke(controller, this, {
'$scope': scope,
'$element': element,
'$attrs': attrs
});
}];
return $delegate;
});
$provide.decorator('formDirective', function($delegate) {
var form = $delegate[0], controller = form.controller;
form.controller = ['$scope', '$element', '$attrs', '$injector', function(scope, element, attrs, $injector) {
var $interpolate = $injector.get('$interpolate');
attrs.$set('name', $interpolate(attrs.name || attrs.ngForm || '')(scope));
$injector.invoke(controller, this, {
'$scope': scope,
'$element': element,
'$attrs': attrs
});
}];
return $delegate;
});
}]);

Demo on JSFiddle.

I expand the @caitp and @Thinkscape solution a bit, to allow dynamically created nested ng-forms, like this:

<div ng-controller="ctrl">
<ng-form name="form">
<input type="text" ng-model="static" name="static"/>


<div ng-repeat="df in dynamicForms">
<ng-form name="form\{\{df.id}}">
<input type="text" ng-model="df.sub" name="sub"/>
<div>Dirty: <span ng-bind="form\{\{df.id}}.$dirty"></span></div>
</ng-form>
</div>


<div><button ng-click="consoleLog()">Console Log</button></div>
<div>Dirty: <span ng-bind="form.$dirty"></span></div>
</ng-form>
</div>

Here is my demo on JSFiddle.

Nice one by @EnISeeK.... but i got it to be more elegant and less obtrusive to other directives:

.directive("dynamicName",[function(){
return {
restrict:"A",
require: ['ngModel', '^form'],
link:function(scope,element,attrs,ctrls){
ctrls[0].$name = scope.$eval(attrs.dynamicName) || attrs.dynamicName;
ctrls[1].$addControl(ctrls[0]);
}
};
}])

I used Ben Lesh's solution and it works well for me. But one problem I faced was that when I added an inner form using ng-form, all of the form states e.g. form.$valid, form.$error etc became undefined if I was using the ng-submit directive.

So if I had this for example:

<form novalidate ng-submit="saveRecord()" name="outerForm">
<!--parts of the outer form-->
<ng-form name="inner-form">
<input name="someInput">
</ng-form>
<button type="submit">Submit</button>
</form>

And in the my controller:

$scope.saveRecord = function() {
outerForm.$valid // this is undefined
}

So I had to go back to using a regular click event for submitting the form in which case it's necessary to pass the form object:

<form novalidate name="outerForm">  <!--remove the ng-submit directive-->
<!--parts of the outer form-->
<ng-form name="inner-form">
<input name="someInput">
</ng-form>
<button type="submit" ng-click="saveRecord(outerForm)">Submit</button>
</form>

And the revised controller method:

$scope.saveRecord = function(outerForm) {
outerForm.$valid // this works
}

I'm not quite sure why this is but hopefully it helps someone.

This issue has been fixed in Angular 1.3+ This is the correct syntax for what you are trying to do:

login[input.name].$invalid

if we set dynamic name for a input like the below

<input name="\{\{dynamicInputName}}" />

then we have use set validation for dynamic name like the below code.

<div ng-messages="login.dynamicInputName.$error">
<div ng-message="required">
</div>
</div>