如何验证使用ng-repeat,ng-show(角度)动态创建的输入


167

我有一个使用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>

我试过{{}}从索引中删除from,但是那也不行。到目前为止,输入的验证属性可以正常工作,但是不会显示错误消息。

有人有什么建议吗?

编辑:除了以下出色的答案之外,这是一篇博客文章,更详细地介绍了此问题:http : //www.thebhwgroup.com/blog/2014/08/angularjs-html-form-design-part-2 /


4
对于那些在2015年阅读此书的人来说...投票最多的答案不再是正确的答案。往下看 :)
威尔·斯特罗尔

似乎是对“2015”的答案@WillStrohl会谈。
osiris 2015年

什么是适当的礼节?我应该保留接受的答案,因为当时的答案是正确的,还是应该接受今天的正确答案?只希望这个看似受欢迎的主题对新访客有用。
PFranchise

@PFranchise,我不知道,但是我认为关于它的明显注释会有所帮助。也许是对您的问题的修改,所以该注释留在更多的人可以看到的地方。
osiris

Answers:


197

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

不幸的是,到今天为止,不可能(不使用自定义指令)动态生成输入的名称。确实,检查输入文档,我们可以看到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/


2
那很棒。但是,具有相同名称的多个文本框是否有效?
伊恩·沃伯顿

1
嵌套表单不被视为有效的HTML stackoverflow.com/questions/379610/can-you-nest-html-forms 角度规划是否可以解决此问题?
Blowsie

11
@Blowsie您不是在这里嵌套真实的形式,而是嵌套ng-formDOM元素,因此,与另一个SO问题的链接在这里不相关。
pkozlowski.opensource,2013年

7
大。应该注意的是,如果您ng-repeat被绑定,table tr则必须使用ng-form="myname"attr。
ivkremer 2014年

11
此答案应进行编辑:自AngularJS 1.3.0(2014
tanguy_k 2015年

228

自从提出问题以来,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>

演示版

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>

2
这是完美的,并且比执行指令要容易得多-可以将表单传递到组件中并使用此方法。谢了哥们!
dinkydani

我注意到,如果您希望此名称有效,则您的表单名称不能带有连字符。有人知道这是为什么吗?
Patrick Szalapski

@PatrickSzalapski:这是因为Angular使用了表单名称,带连字符的变量名称在Javascript中不是有效的语法。解决方法:<span ng-show =“ vm ['my-form'] ['person_'+ $ index]。$ invalid”>输入名称</ span>
HoffZ

我注意到,如果您动态删除重复的项目,$valid则输入的属性将错误地获取false
jonathanwiesel

您希望将所有错误显示在表格顶部的某个位置吗?
–codingbbq

13

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

如果要同时使用其他指令,请注意不要设置“ terminal”属性,否则该函数将无法运行(假设优先级为-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

Github关于ng选项猴子补丁的需求的评论: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;
            }
          }
        }
      }
    };
});

3
为了清楚起见,未选择此答案并不表示它不是最佳答案。它最初是在提出问题的近两年后才发布的。如果您遇到相同的问题,除了选择的答案,我还将考虑该答案和tomGreen的答案。
PFranchise 2014年

11

在使用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)
Jesper Tejlgaard,2015年

+1这个答案对我有用, 请检查链接:您只需要添加ng-form="formName"到具有ng-repeat的标签中即可...它的工作就像一种魅力:)
Abdellah Alaoui

3

在控制器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>

1

纵观这些解决方案,上面的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上有一个更完整的工作示例。


1

如果我使用以下语法,验证将与ng repeat一起使用,但scope.step3Form['item[107][quantity]'].$touched 我不知道这是最佳实践还是最佳解决方案,但是它有效

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

1

在pkozlowski.opensource的答案的基础上,我添加了一种方法,使动态输入名称也可以与ngMessages一起使用。注意元素的ng-init一部分ng-form和的使用furryNamefurryName成为包含该变量值的变量名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>

1

为时已晚,但可能可以帮助任何人

  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>

这里查看工作演示


1

如果您使用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">

0

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

把桌子包裹成这样的形式

然后用这个

我有一个带有多嵌套指令的表单,这些指令都包含输入,选择等。这些元素都包含在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


0

这将在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> 


0

这是我如何执行此操作的示例,我不知道这是否是最佳解决方案,但效果很好。

首先,用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);
       };

0

我的要求与原始问题的要求有所不同,但是希望我可以帮助遇到同样问题的人。

我必须根据作用域变量来定义是否需要一个字段。因此,我基本上必须进行设置ng-required="myScopeVariable"(这是一个布尔变量)。

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