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


98

我有这种形式:http : //jsfiddle.net/dfJeN/

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

name="username"

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

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

name="{input.name}"

然后我将其应用于我的验证

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

(此模式将在ng-repeat中使用),但我的表单验证已损坏。它在我的浏览器中已正确解释(如果检查元素,我看到login.username。$ error.required)。

任何想法 ?

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

{{input.name}}

表达式不是内插的。我的表单是{{input.name}}属性,但没有用户名。

更新:由于1.3.0-rc.3 name =“ {{input.name}}”“可以正常工作。请参阅#1404


经过一番研究,我发现:“有一次需要使用ngBind而不是{{expression}}绑定的情况是,需要将绑定放入模板,该模板在Angular编译之前由浏览器暂时以其原始状态显示。 。在docs.angularjs.org/api/ng.directive:ngBind此页面中,这似乎是我尝试做的一个很好的开始。如果我找到解决方案,这篇文章将被更新。
IxDay 2013年

有一个已打开的github问题github.com/angular/angular.js/issues/1404
Yaroslav

有任何答案可以解决您的问题。如果是这样,请单击下面的分数标记,将其标记为答案。
里卡多·索扎

这是一篇博客文章,可能会对遇到此问题的其他人有所帮助:thebhwgroup.com/blog/2014/08/angularjs-html-form-design-part-2
PFranchise 2014年

Answers:


176

您不能那样做。

假设您要执行的操作是使用ng-repeat之类的元素向表单中动态添加元素,则需要使用嵌套的ng-form来验证这些单独的项目:

<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>

令人遗憾的是,这并不是Angular的功能齐全的功能。


11
最终如何解决这个问题?我仍然看不到这个特定答案与您的问题有什么关系-因为它没有显示动态生成的表单字段和名称?
Oddman

7
这是一个完整的解决方案(或解决方法),也是角度小组建议的方法(来自docs.angularjs.org/api/ng.directive:form):“由于无法使用内插法动态生成输入元素的名称属性,因此,必须将每组重复的输入包装在ngForm指令中,并将其嵌套在外部form元素中。” 每个嵌套表单都有其自己的作用域,可以使它起作用。
Noremac 2013年

2
此示例和建议仍未解决动态“名称”。看起来他们希望允许您动态嵌套“克隆”的字段集,但每个字段的基础名称必须是静态的。
Thinice 2014年

2
@thinice是的,它确实有帮助。使用此解决方案,名称不必是动态的。它可以是任何您喜欢的东西(例如“ foo”)。关键是子窗体具有其自己的作用域,因此验证表达式可以仅引用innerForm.foo。$ error等。然后,ng-model可以指向父作用域中您希望它指向的任何对象(可能是动态的)。
杰德·理查兹

@thinice-Wintamute是正确的。无需动态名称,因为您无需直接提交表单。目的是更改某些模型,然后通过Ajax发布。动态名称在那时不会给您任何帮助。如果您实际上使用的是HTML表单提交,那么您所做的事情很奇怪/错误,您将需要使用其他方法。
Ben Lesh 2014年

44

使用嵌套的ngForm允许您从HTML模板中访问特定的InputController。但是,如果您希望从另一个控制器访问它,则无济于事。

例如

<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>

我使用此指令来帮助解决问题:

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);
    }
  };
});

现在,您只需在需要的地方使用“动态名称”属性即可使用动态名称,而无需使用“名称”属性。

例如

<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>

1
我使用此解决方案,除了使用$interpolate代替之外$parse,感觉更有用
TheRocketSurgeon 2014年

我看到你做Terminal:是的。那是什么意思?我也可以在表单上使用此指令<form ng-repeat="item in items" dynamic-name="'item'+item.id"> ... <span ng-show="item{{item.id}}.$invalid">This form is invalid</span></form>吗?
felixfbecker 2015年

16

根据有关Github的讨论,该问题应在AngularJS 1.3中解决。

同时,这是@caitp@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;
    });
}]);

JSFiddle上的演示。


1
对于那些使用ng 1.2的人来说,这很容易成为“ hacky”最少的修复程序。
手榴弹

14

@EnISeeK的一个不错的选择。

.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]);
        }
    };
}])

1
我只添加以下内容。ctrls [0]。$ name = scope。$ eval(attrs.dynamicName)|| attrs.dynamicName;
GnrlBzik 2014年

7

只是对EnlSeek解决方案的一点改进

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);
    }]

  };
}]);

这是一次小小的尝试这是详细的移植


+ 1,EnlSeek的指令在我的指令中导致无限循环;不过,我必须删除此答案中的“ fx”部分才能正常工作
约翰·

优先级会干扰一组名称相同但具有ng-if的字段。例如:<input type ='text'dynamic-name ='foo'ng-if ='field.type ==“ text” /> <textarea dynamic-name ='foo'ng-if ='field.type == “ textarea”> </ textarea>删除'priority:10000'对我来说解决了这个问题,并且仍然可以正常运行。
Thinice 2014年

ngIf的优先级为600。为此指令分配的优先级小于600,应使其与ngIf一起使用。
杰森·张

如果未设置优先级(默认为0),则如果此指令在ngModel之前求值,则可以与ngModel(优先级0)一起使用。您想给它一个优先级,以便始终在编译/链接ngModel之前。
杰森·张

5

我稍微扩展了@caitp和@Thinkscape解决方案,以允许动态创建嵌套的ng-forms,如下所示:

<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>

这是我在JSFiddle上的演示。


4

我使用了Ben Lesh的解决方案,对我来说效果很好。但是我遇到的一个问题是,当我使用时添加一个内部表单时ng-formform.$valid, form.$error如果我正在使用ng-submit指令,则所有表单状态(例如etc)都将变得不确定。

所以如果我有这个例子:

<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>

在我的控制器中:

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

因此,我不得不回过头来使用常规的click事件来提交表单,在这种情况下,有必要传递表单对象:

<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>

以及修改后的控制器方法:

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

我不太确定为什么会这样,但希望它能对某人有所帮助。


3

Angular 1.3+已修复此问题。这是您尝试执行的正确语法:

login[input.name].$invalid

0

如果我们为如下所示的输入设置动态名称

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

那么我们就可以对动态名称使用集合验证,如以下代码所示。

<div ng-messages="login.dynamicInputName.$error">
   <div ng-message="required">
   </div>
</div>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.