【问题标题】:How to validate inputs dynamically created using ng-repeat, ng-show (angular)如何验证使用 ng-repeat、ng-show (angular) 动态创建的输入
【发布时间】:2012-08-16 04:10:21
【问题描述】:

我有一个使用 ng-repeat 创建的表。我想为表中的每个元素添加验证。问题是每个输入单元格与它上面和下面的单元格具有相同的名称。我尝试使用 {{$index}} 值来命名输入,但尽管 HTML 中的字符串文字看起来正确,但它现在正在工作。

这是我现在的代码:

<tr ng-repeat="r in model.BSM ">
   <td>
      <input ng-model="r.QTY" class="span1" name="QTY{{$index}}" ng-pattern="/^[\d]*\.?[\d]*$/" required/>
      <span class="alert-error" ng-show="form.QTY{{$index}}.$error.pattern"><strong>Requires a number.</strong></span>
      <span class="alert-error" ng-show="form.QTY{{$index}}.$error.required"><strong>*Required</strong></span>
   </td>
</tr>

我尝试从索引中删除{{}},但这也不起作用。到目前为止,输入的验证属性工作正常,但没有显示错误消息。

大家有什么建议吗?

编辑:除了下面的精彩答案之外,这里还有一篇博客文章更详细地介绍了这个问题:http://www.thebhwgroup.com/blog/2014/08/angularjs-html-form-design-part-2/

【问题讨论】:

  • 对于那些在 2015 年阅读这篇文章的人......投票最多的答案不再是正确的答案。往下看。 :)
  • This 似乎是@WillStrohl 谈到的“2015 年”答案。
  • 这里的礼仪是什么?我应该留下接受的答案,因为当时它是正确的,还是接受今天的正确答案?只是希望这个看似流行的主题对新访问者有所帮助。
  • @PFranchise,我不知道,但我认为关于它的可见说明可能会有所帮助。也许作为对您的问题的修改,以便将注释保留在更多人可以看到的地方。

标签: javascript html angularjs validation grid


【解决方案1】:

AngularJS 依赖输入名称来暴露验证错误。

不幸的是,截至今天,动态生成输入名称是不可能的(不使用自定义指令)。事实上,检查input docs 我们可以看到name 属性只接受一个字符串。

要解决“动态名称”问题您需要创建一个内部表单(请参阅ng-form

<div ng-repeat="social in formData.socials">
      <ng-form name="urlForm">
            <input type="url" name="socialUrl" ng-model="social.url">
            <span class="alert error" ng-show="urlForm.socialUrl.$error.url">URL error</span>
      </ng-form>
  </div>

另一种选择是为此编写自定义指令。

这里是显示 ngForm 用法的 jsFiddle:http://jsfiddle.net/pkozlowski_opensource/XK2ZT/2/

【讨论】:

  • 太好了。但是有多个同名的文本框是有效的html吗?
  • 嵌套表单不被认为是有效的 HTML stackoverflow.com/questions/379610/can-you-nest-html-forms 角度规划是否可以解决此问题?
  • @Blowsie 你不是在这里嵌套真实的形式,而是ng-form DOM 元素,所以到其他 SO 问题的链接在这里不相关。
  • 太棒了。应该注意的是,如果您的ng-repeat 绑定在table tr 上,那么您必须使用ng-form="myname" attr。
  • 这个答案应该被编辑:问题github.com/angular/angular.js/issues/1404自AngularJS 1.3.0以来已经解决(从2014年9月提交)
【解决方案2】:

在控制器 http://jsfiddle.net/82PX4/3/ 一侧添加了更复杂的“自定义验证”示例

<div class='line' ng-repeat='line in ranges' ng-form='lineForm'>
    low: <input type='text' 
                name='low'
                ng-pattern='/^\d+$/' 
                ng-change="lowChanged(this, $index)" ng-model='line.low' />
    up: <input type='text' 
                name='up'
                ng-pattern='/^\d+$/'
                ng-change="upChanged(this, $index)" 
                ng-model='line.up' />
    <a href ng-if='!$first' ng-click='removeRange($index)'>Delete</a>
    <div class='error' ng-show='lineForm.$error.pattern'>
        Must be a number.
    </div>
    <div class='error' ng-show='lineForm.$error.range'>
        Low must be less the Up.
    </div>
</div>

【讨论】:

    【解决方案3】:

    在使用 ng-repeat 指令的标签内使用 ng-form 指令。然后,您可以使用 ng-form 指令创建的范围来引用通用名称。例如:

        <div class="form-group col-sm-6" data-ng-form="subForm" data-ng-repeat="field in justificationInfo.justifications"">
    
            <label for="{{field.label}}"><h3>{{field.label}}</h3></label>
            <i class="icon-valid" data-ng-show="subForm.input.$dirty && subForm.input.$valid"></i>
            <i class="icon-invalid" data-ng-show="subForm.input.$dirty && subForm.input.$invalid"></i>
            <textarea placeholder="{{field.placeholder}}" class="form-control" id="{{field.label}}" name="input" type="text" rows="3" data-ng-model="field.value" required>{{field.value}}</textarea>
    
        </div>
    

    归功于:http://www.benlesh.com/2013/03/angular-js-validating-form-elements-in.html

    【讨论】:

    • 接受的答案对我不起作用。然而,这个做到了。 (我使用 Angular 2.1.14)
    • +1 这个答案对我有用 检查链接:您只需将 ng-form="formName" 添加到具有 ng-repeat 的标签中......它就像一个魅力:)
    【解决方案4】:

    如果您不想使用 ng-form,您可以使用自定义指令来更改表单的 name 属性。将此指令作为属性放置在与您的 ng-model 相同的元素上。

    如果您同时使用其他指令,请注意它们没有设置“终端”属性,否则此函数将无法运行(假设它的优先级为 -1)。

    例如,当将此指令与 ng-options 一起使用时,您必须运行这一行monkeypatch: https://github.com/AlJohri/bower-angular/commit/eb17a967b7973eb7fc1124b024aa8b3ca540a155

    angular.module('app').directive('fieldNameHack', function() {
        return {
          restrict: 'A',
          priority: -1,
          require: ['ngModel'],
          // the ngModelDirective has a priority of 0.
          // priority is run in reverse order for postLink functions.
          link: function (scope, iElement, iAttrs, ctrls) {
    
            var name = iElement[0].name;
            name = name.replace(/\{\{\$index\}\}/g, scope.$index);
    
            var modelCtrl = ctrls[0];
            modelCtrl.$name = name;
    
          }
        };
    });
    

    我经常发现使用 ng-init 将 $index 设置为变量名很有用。例如:

    <fieldset class='inputs' ng-repeat="question questions" ng-init="qIndex = $index">
    

    这会将您的正则表达式更改为:

    name = name.replace(/\{\{qIndex\}\}/g, scope.qIndex);
    

    如果您有多个嵌套的 ng-repeats,您现在可以使用这些变量名来代替 $parent.$index。

    指令的“终端”和“优先级”定义:https://docs.angularjs.org/api/ng/service/$compile#directive-definition-object

    关于需要 ng-option monkeypatch 的 Github 评论: https://github.com/angular/angular.js/commit/9ee2cdff44e7d496774b340de816344126c457b3#commitcomment-6832095 https://twitter.com/aljohri/status/482963541520314369

    更新:

    您也可以使用 ng-form 进行这项工作。

    angular.module('app').directive('formNameHack', function() {
        return {
          restrict: 'A',
          priority: 0,
          require: ['form'],
          compile: function() {
            return {
              pre: function(scope, iElement, iAttrs, ctrls) {
                var parentForm = $(iElement).parent().controller('form');
                if (parentForm) {
                    var formCtrl = ctrls[0];
                    delete parentForm[formCtrl.$name];
                    formCtrl.$name = formCtrl.$name.replace(/\{\{\$index\}\}/g, scope.$index);
                    parentForm[formCtrl.$name] = formCtrl;
                }
              }
            }
          }
        };
    });
    

    【讨论】:

    • 澄清一下,这个答案未被选中,并不表示它不是最佳答案。它是在最初提出问题近 2 年后才发布的。如果您遇到同样的问题,除了所选答案之外,我还会考虑这个答案和 tomGreen 的答案。
    【解决方案5】:

    查看这些解决方案,上面 Al Johri 提供的解决方案最符合我的需求,但他的指令没有我想要的可编程性。这是我对他的解决方案的版本:

    angular.module("app", [])
        .directive("dynamicFormName", function() {
            return {
                restrict: "A",
                priority: 0,
                require: ["form"],
                compile: function() {
                    return {
                        pre: function preLink(scope, iElement, iAttrs, ctrls) {
                            var name = "field" + scope.$index;
    
                            if (iAttrs.dnfnNameExpression) {
                                name = scope.$eval(iAttrs.dnfnNameExpression);
                            }
    
                            var parentForm = iElement.parent().controller("form");
                            if (parentForm) {
                                var formCtrl = ctrls[0];
                                delete parentForm[formCtrl.$name];
                                formCtrl.$name = name;
                                parentForm[formCtrl.$name] = formCtrl;
                            }
                        }
                     }
                }
            };
       });
    

    此解决方案让您只需将名称生成器表达式传递给指令,并避免锁定到他正在使用的模式替换。

    我最初使用此解决方案时也遇到了麻烦,因为它没有显示在标记中使用它的示例,所以这就是我的使用方法。

    <form name="theForm">
        <div ng-repeat="field in fields">
            <input type="number" ng-form name="theInput{{field.id}}" ng-model="field.value" dynamic-form-name dnfn-name-expression="'theInput' + field.id">        
        </div>
    </form>
    

    我在github 上有一个更完整的工作示例。

    【讨论】:

      【解决方案6】:

      这是可能的,这就是我如何使用输入表做同样的事情。

      将表格包装成这样的形式

      那就用这个吧

      我有一个包含多嵌套指令的表单,所有指令都包含输入、选择等... 这些元素都包含在 ng-repeats 和动态字符串值中。

      这是指令的使用方法:

      <form name="myFormName">
        <nested directives of many levels>
          <your table here>
          <perhaps a td here>
          ex: <input ng-repeat=(index, variable) in variables" type="text"
                     my-name="{{ variable.name + '/' + 'myFormName' }}"
                     ng-model="variable.name" required />
          ex: <select ng-model="variable.name" ng-options="label in label in {{ variable.options }}"
                      my-name="{{ variable.name + index + '/' + 'myFormName' }}"
              </select>
      </form>
      

      注意:如果您可能需要序列化输入表,您可以添加和索引字符串连接;我就是这么做的。

      app.directive('myName', function(){
      
        var myNameError = "myName directive error: "
      
        return {
          restrict:'A', // Declares an Attributes Directive.
          require: 'ngModel', // ngModelController.
      
          link: function( scope, elem, attrs, ngModel ){
            if( !ngModel ){ return } // no ngModel exists for this element
      
            // check myName input for proper formatting ex. something/something
            checkInputFormat(attrs);
      
            var inputName = attrs.myName.match('^\\w+').pop(); // match upto '/'
            assignInputNameToInputModel(inputName, ngModel);
      
            var formName = attrs.myName.match('\\w+$').pop(); // match after '/'
            findForm(formName, ngModel, scope);
          } // end link
        } // end return
      
        function checkInputFormat(attrs){
          if( !/\w\/\w/.test(attrs.rsName )){
            throw myNameError + "Formatting should be \"inputName/formName\" but is " + attrs.rsName
          }
        }
      
        function assignInputNameToInputModel(inputName, ngModel){
          ngModel.$name = inputName
        }
      
        function addInputNameToForm(formName, ngModel, scope){
          scope[formName][ngModel.$name] = ngModel; return
        }
      
        function findForm(formName, ngModel, scope){
          if( !scope ){ // ran out of scope before finding scope[formName]
            throw myNameError + "<Form> element named " + formName + " could not be found."
          }
      
          if( formName in scope){ // found scope[formName]
            addInputNameToForm(formName, ngModel, scope)
            return
          }
          findForm(formName, ngModel, scope.$parent) // recursively search through $parent scopes
        }
      });
      

      这应该可以处理许多您只是不知道表单在哪里的情况。或者您可能有嵌套的表单,但出于某种原因您想将此输入名称附加到两个表单上?好吧,只需传入要附加输入名称的表单名称即可。

      我想要的是一种将动态值分配给我永远不会知道的输入的方法,然后只需调用 $scope.myFormName.$valid。

      你可以添加任何你想要的东西:更多的表格更多的表单输入、嵌套的表单,任何你想要的。只需传递您要验证输入的表单名称。然后在表单提交时询问 $scope.yourFormName.$valid

      【讨论】:

        【解决方案7】:

        这将使 ng-repeat 中的名称在表单验证中单独出现。

        <td>
            <input ng-model="r.QTY" class="span1" name="{{'QTY' + $index}}" ng-pattern="/^[\d]*\.?[\d]*$/" required/>
        </td>
        

        但我无法让它在其验证消息中查找,所以我不得不使用 ng-init 来让它将变量解析为对象键。

        <td>
            <input ng-model="r.QTY" class="span1" ng-init="name = 'QTY' + $index" name="{{name}}" ng-pattern="/^[\d]*\.?[\d]*$/" required/>
            <span class="alert-error" ng-show="form[name].$error.pattern"><strong>Requires a number.</strong></span>
            <span class="alert-error" ng-show="form[name].$error.required"><strong>*Required</strong></span> 
        

        【讨论】:

          【解决方案8】:

          如果我使用以下语法scope.step3Form['item[107][quantity]'].$touched,验证正在使用 ng repeat 我不知道这是最佳做法或最佳解决方案,但它确实有效

          <tr ng-repeat="item in items">
             <td>
                  <div class="form-group">
                      <input type="text" ng-model="item.quantity" name="item[<% item.id%>][quantity]" required="" class="form-control" placeholder = "# of Units" />
                      <span ng-show="step3Form.$submitted || step3Form['item[<% item.id %>][quantity]'].$touched">
                          <span class="help-block" ng-show="step3Form['item[<% item.id %>][quantity]'].$error.required"> # of Units is required.</span>
                      </span>
                  </div>
              </td>
          </tr>
          

          【讨论】:

            【解决方案9】:

            在 pkozlowski.opensource 的 answer 的基础上,我添加了一种动态输入名称的方法,该名称也适用于 ngMessages。注意ng-form 元素上的ng-init 部分和furryName 的使用。 furryName 成为包含 inputname 属性的变量值的变量名称。

            <ion-item ng-repeat="animal in creatures track by $index">
            <ng-form name="animalsForm" ng-init="furryName = 'furry' + $index">
                    <!-- animal is furry toggle buttons -->
                    <input id="furryRadio{{$index}}"
                           type="radio"
                           name="{{furryName}}"
                           ng-model="animal.isFurry"
                           ng-value="radioBoolValues.boolTrue"
                           required
                            >
                    <label for="furryRadio{{$index}}">Furry</label>
            
                    <input id="hairlessRadio{{$index}}"
                           name="{{furryName}}"
                           type="radio"
                           ng-model="animal.isFurry"
                           ng-value="radioBoolValues.boolFalse"
                           required
                           >
                    <label for="hairlessRadio{{$index}}">Hairless</label>
            
                    <div ng-messages="animalsForm[furryName].$error"
                         class="form-errors"
                         ng-show="animalsForm[furryName].$invalid && sectionForm.$submitted">
                        <div ng-messages-include="client/views/partials/form-errors.ng.html"></div>
                    </div>
            </ng-form>
            </ion-item>
            

            【讨论】:

              【解决方案10】:

              这是我如何做到这一点的一个例子,我不知道它是否是最好的解决方案,但效果很好。

              首先,HTML 中的代码。 看看 ng-class,它正在调用 hasError 函数。 还要查看输入的名称声明。我使用 $index 来创建不同的输入名称。

              <div data-ng-repeat="tipo in currentObject.Tipo"
                  ng-class="{'has-error': hasError(planForm, 'TipoM', 'required', $index) || hasError(planForm, 'TipoM', 'maxlength', $index)}">
                  <input ng-model="tipo.Nombre" maxlength="100" required
                      name="{{'TipoM' + $index}}"/>
              

              现在,这里是 hasError 函数:

              $scope.hasError = function (form, elementName, errorType, index) {
                         if (form == undefined
                             || elementName == undefined
                             || errorType == undefined
                             || index == undefined)
                             return false;
              
                         var element = form[elementName + index];
                         return (element != null && element.$error[errorType] && element.$touched);
                     };
              

              【讨论】:

                【解决方案11】:

                自从提出这个问题以来,Angular 团队已经解决了这个问题,让动态创建输入名称成为可能。

                使用 Angular 1.3 及更高版本,您现在可以这样做:

                <form name="vm.myForm" novalidate>
                  <div ng-repeat="p in vm.persons">
                    <input type="text" name="person_{{$index}}" ng-model="p" required>
                    <span ng-show="vm.myForm['person_' + $index].$invalid">Enter a name</span>
                  </div>
                </form>
                

                Demo

                Angular 1.3 还引入了 ngMessages,一个更强大的表单验证工具。您可以对 ngMessages 使用相同的技术:

                <form name="vm.myFormNgMsg" novalidate>
                    <div ng-repeat="p in vm.persons">
                      <input type="text" name="person_{{$index}}" ng-model="p" required>
                      <span ng-messages="vm.myFormNgMsg['person_' + $index].$error">
                        <span ng-message="required">Enter a name</span>
                      </span>
                    </div>
                  </form>
                

                【讨论】:

                • 这比做指令更完美,也更容易——可以将表单传递给组件并使用此方法。谢谢老哥!
                • 我注意到如果你想让它工作,你的表单名称不能有连字符。有谁知道这是为什么?
                • @PatrickSzalapski:这是因为 Angular 使用了表单名称,并且带有连字符的变量名称在 Javascript 中是无效的语法。解决方法:输入名称
                • 我注意到,如果您动态删除重复项,则输入的 $valid 属性会错误地得到 false
                • 您希望所有错误都显示在表单顶部的一个地方吗?
                【解决方案12】:

                我的要求与最初的问题有所不同,但希望我能帮助遇到与我相同问题的人..

                我必须根据范围变量来定义是否需要字段。所以我基本上必须设置ng-required="myScopeVariable"(这是一个布尔变量)。

                <div class="align-left" ng-repeat="schema in schemas">
                    <input type="text" ng-required="schema.Required" />
                </div>
                

                【讨论】:

                  【解决方案13】:

                  为时已晚,但可能对任何人都有帮助

                  1. 为每个控件创建唯一名称
                  2. 使用fromname[uniquname].$error 进行验证

                  示例代码:

                  <input 
                      ng-model="r.QTY" 
                      class="span1" 
                      name="QTY{{$index}}" 
                      ng-pattern="/^[\d]*\.?[\d]*$/" required/>
                  <div ng-messages="formName['QTY' +$index].$error"
                       ng-show="formName['QTY' +$index].$dirty || formName.$submitted">
                     <div ng-message="required" class='error'>Required</div>
                     <div ng-message="pattern" class='error'>Invalid Pattern</div>
                  </div>
                  

                  查看工作demo here

                  【讨论】:

                    【解决方案14】:

                    如果你使用 ng-repeat $index 像这样工作

                      name="QTY{{$index}}"
                    

                       <td>
                           <input ng-model="r.QTY" class="span1" name="QTY{{$index}}" ng-            
                            pattern="/^[\d]*\.?[\d]*$/" required/>
                            <span class="alert-error" ng-show="form['QTY' + $index].$error.pattern">
                            <strong>Requires a number.</strong></span>
                            <span class="alert-error" ng-show="form['QTY' + $index].$error.required">
                           <strong>*Required</strong></span>
                        </td>
                    

                    我们必须在 ng-pattern 中显示 ng-show

                       <span class="alert-error" ng-show="form['QTY' + $index].$error.pattern">
                       <span class="alert-error" ng-show="form['QTY' + $index].$error.required">
                    

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2014-08-24
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多