简体   繁体   English

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

[英]How to validate inputs dynamically created using ng-repeat, ng-show (angular)

I have a table that is created using ng-repeat.我有一个使用 ng-repeat 创建的表。 I want to add validation to each element in the table.我想为表中的每个元素添加验证。 The problem is that each input cell has the same name as the cell above and below it.问题是每个输入单元格都与其上方和下方的单元格具有相同的名称。 I attempted to use the {{$index}} value to name the inputs, but despite the string literals in HTML appearing correct, it is now working.我尝试使用{{$index}}值来命名输入,但尽管 HTML 中的字符串文字看起来是正确的,但它现在正在工作。

Here is my code as of now:这是我现在的代码:

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

I have tried removing the {{}} from index, but that does not work either.我曾尝试从索引中删除{{}} ,但这也不起作用。 As of now, the validation property of the input is working correctly, but the error message is not displayed.截至目前,输入的验证属性工作正常,但未显示错误消息。

Anyone have any suggestions?有人有什么建议吗?

Edit: In addition to the great answers below, here is a blog article that covers this issue in more detail: http://www.thebhwgroup.com/blog/2014/08/angularjs-html-form-design-part-2/编辑:除了下面的好答案,这里有一篇博客文章更详细地介绍了这个问题: http : //www.thebhwgroup.com/blog/2014/08/angularjs-html-form-design-part-2 /

Since the question was asked the Angular team has solved this issue by making it possible to dynamically create input names.自从提出这个问题以来,Angular 团队已经通过动态创建输入名称解决了这个问题。

With Angular version 1.3 and later you can now do this:使用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 also introduced ngMessages, a more powerful tool for form validation. Angular 1.3 还引入了 ngMessages,一种更强大的表单验证工具。 You can use the same technique with 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>

AngularJS relies on input names to expose validation errors. AngularJS 依赖输入名称来暴露验证错误。

Unfortunately, as of today, it is not possible (without using a custom directive) to dynamically generate a name of an input.不幸的是,截至今天,(不使用自定义指令)动态生成输入的名称是不可能的。 Indeed, checking input docs we can see that the name attribute accepts a string only.事实上,检查输入文档我们可以看到 name 属性只接受一个字符串。

To solve the 'dynamic name' problem you need to create an inner form (see ng-form ) :要解决“动态名称”问题,您需要创建一个内部表单(请参阅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>

The other alternative would be to write a custom directive for this.另一种选择是为此编写自定义指令。

Here is the jsFiddle showing the usage of the ngForm: http://jsfiddle.net/pkozlowski_opensource/XK2ZT/2/这是显示 ngForm 用法的 jsFiddle: http : //jsfiddle.net/pkozlowski_opensource/XK2ZT/2/

If you don't want to use ng-form you can use a custom directive that will change the form's name attribute.如果您不想使用 ng-form,您可以使用自定义指令来更改表单的 name 属性。 Place this directive as an attribute on the same element as your ng-model.将此指令作为属性放在与 ng-model 相同的元素上。

If you're using other directives in conjunction, be careful that they don't have the "terminal" property set otherwise this function won't be able to run (given that it has a priority of -1).如果您结合使用其他指令,请注意它们没有设置“终端”属性,否则此函数将无法运行(假设它的优先级为 -1)。

For example, when using this directive with ng-options, you must run this one line monkeypatch: https://github.com/AlJohri/bower-angular/commit/eb17a967b7973eb7fc1124b024aa8b3ca540a155例如,当将此指令与 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;

      }
    };
});

I often find it useful to use ng-init to set the $index to a variable name.我经常发现使用 ng-init 将 $index 设置为变量名很有用。 For example:例如:

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

This changes your regular expression to:这会将您的正则表达式更改为:

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

If you have multiple nested ng-repeats, you can now use these variable names instead of $parent.$index.如果您有多个嵌套的 ng-repeat,您现在可以使用这些变量名称代替 $parent.$index。

Definition of "terminal" and "priority" for directives: https://docs.angularjs.org/api/ng/service/ $compile#directive-definition-object指令的“终端”和“优先级”的定义: https : //docs.angularjs.org/api/ng/service/ $compile#directive-definition-object

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

UPDATE:更新:

You can also make this work with ng-form.您也可以使用 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;
            }
          }
        }
      }
    };
});

Use the ng-form directive inside of the tag in which you are using the ng-repeat directive.在使用 ng-repeat 指令的标签内使用 ng-form 指令。 You can then use the scope created by the ng-form directive to reference a generic name.然后,您可以使用 ng-form 指令创建的范围来引用通用名称。 For example:例如:

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

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

Added more complex example with "custom validation" on the side of controller http://jsfiddle.net/82PX4/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>

Looking over these solutions, the one provided by Al Johri above is the closest to my needs, but his directive was a little less programmable then I wanted.查看这些解决方案,上面 Al Johri 提供的解决方案最接近我的需求,但他的指令比我想要的可编程性要差一些。 Here is my version of his solutions:这是我的解决方案版本:

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

This solution lets you just pass a name generator expression to the directive and avoids the lock down to pattern substitution he was using.此解决方案让您只需将名称生成器表达式传递给指令,并避免锁定他正在使用的模式替换。

I also had trouble initially with this solution since it didn't show an example of using it in markup, so here is how I used it.我最初也遇到了这个解决方案的问题,因为它没有显示在标记中使用它的例子,所以这里是我如何使用它。

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

I have a more complete working example on github .我在github上有一个更完整的工作示例。

validation is working with ng repeat if I use the following syntax scope.step3Form['item[107][quantity]'].$touched I don't know it's a best practice or the best solution, but it works如果我使用以下语法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>

Building on pkozlowski.opensource's answer , I've added a way to have dynamic input names that also work with ngMessages .基于 pkozlowski.opensource's answer ,我添加了一种方法来获得动态输入名称也可以与ngMessages一起 使用 Note the ng-init part on the ng-form element and the use of furryName .注意ng-form元素上的ng-init部分和furryName的使用。 furryName becomes the variable name that contains the variable value for the input 's name attribute. furryName成为包含inputname属性的变量值的变量name

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

Here an example of how I do that, I don't know if it is the best solution, but works perfectly.这是我如何做到这一点的示例,我不知道它是否是最佳解决方案,但效果很好。

First, code in HTML.首先,在 HTML 中编码。 Look at ng-class, it's calling hasError function.看看 ng-class,它正在调用 hasError 函数。 Look also to the input's name declaration.还要查看输入的名称声明。 I use the $index to create different input names.我使用 $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}}"/>

And now, here is the hasError function:现在,这里是 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);
       };

It is too late but might be it can help anyone为时已晚,但可能可以帮助任何人

  1. Create unique name for every control为每个控件创建唯一名称
  2. Validate by using fromname[uniquname].$error使用fromname[uniquname].$error

Sample code:示例代码:

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

See working demo here此处查看工作演示

If your using ng-repeat $index works like this如果您使用 ng-repeat $index 是这样工作的

  name="QTY{{$index}}"

and

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

we have to show the ng-show in ng-pattern我们必须以 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">

It is possible and here is how I do the same thing with a table of inputs.这是可能的,这是我如何用输入表做同样的事情。

wrap the table in a form like so像这样把桌子包起来

Then just use this然后就用这个

I have a form with multi-nested directives that all contain input(s), select(s), etc... These elements are all enclosed in ng-repeats, and dynamic string values.我有一个带有多嵌套指令的表单,所有指令都包含输入、选择等……这些元素都包含在 ng-repeat 和动态字符串值中。

This is how to use the directive:这是如何使用指令:

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

Note: you can add and index to the string concatenation if you need to serialize perhaps a table of inputs;注意:如果您需要序列化输入表,您可以添加和索引字符串连接; which is what I did.这就是我所做的。

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

This should handle many situations where you just don't know where the form will be.这应该可以处理许多您不知道表单在哪里的情况。 Or perhaps you have nested forms, but for some reason you want to attach this input name to two forms up?或者,您可能有嵌套的表单,但出于某种原因,您想将此输入名称附加到两个表单上? Well, just pass in the form name you want to attach the input name to.好吧,只需传入要附加输入名称的表单名称。

What I wanted, was a way to assign dynamic values to inputs that I will never know, and then just call $scope.myFormName.$valid.我想要的是一种将动态值分配给我永远不会知道的输入的方法,然后只需调用 $scope.myFormName.$valid。

You can add anything else you wish: more tables more form inputs, nested forms, whatever you want.您可以添加任何您想要的内容:更多表格、更多表单输入、嵌套表单,以及您想要的任何内容。 Just pass the form name you want to validate the inputs against.只需传递您想要验证输入的表单名称。 Then on form submit ask if the $scope.yourFormName.$valid然后在表单提交上询问 $scope.yourFormName.$valid

This will get the name in the ng-repeat to come up seperate in the form validation.这将使 ng-repeat 中的名称在表单验证中单独出现。

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

But I had trouble getting it to look up in its validation message so I had to use an ng-init to get it to resolve a variable as the object key.但是我很难让它在它的验证消息中查找,所以我不得不使用 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> 

My requirements were a bit different than the ones asked on the original question, but hopefully I might help someone who is going through the same problem that I was..我的要求与原始问题中提出的要求略有不同,但希望我可以帮助遇到与我相同问题的人。

I had to define if a field was required or not based on a scope variable.. So I basically had to set ng-required="myScopeVariable" (which is a boolean variable).我必须根据范围变量定义一个字段是否需要。所以我基本上必须设置ng-required="myScopeVariable" (这是一个布尔变量)。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM