Cách xác thực đầu vào được tạo động bằng cách sử dụng ng-repeat, ng-show (góc)


167

Tôi có một bảng được tạo bằng ng-repeat. Tôi muốn thêm xác nhận cho từng thành phần trong bảng. Vấn đề là mỗi ô đầu vào có cùng tên với ô bên trên và bên dưới nó. Tôi đã cố gắng sử dụng {{$index}}giá trị để đặt tên cho các đầu vào, nhưng mặc dù các chuỗi ký tự trong HTML có vẻ chính xác, nó hiện đang hoạt động.

Đây là mã của tôi cho đến nay:

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

Tôi đã thử xóa {{}}chỉ mục, nhưng điều đó cũng không hoạt động. Đến bây giờ, thuộc tính xác thực của đầu vào đang hoạt động chính xác, nhưng thông báo lỗi không được hiển thị.

Bất cứ ai có bất kỳ đề nghị?

Chỉnh sửa: Ngoài các câu trả lời tuyệt vời dưới đây, đây là một bài viết blog đề cập đến vấn đề này chi tiết hơn: http://www.thebhwgroup.com/blog/2014/08/angularjs-html-form-design-part-2 /


4
Đối với những người đọc điều này trong năm 2015 ... câu trả lời được bình chọn hàng đầu KHÔNG còn là câu trả lời đúng nữa. Nhìn thấp hơn. :)
Sẽ Strohl

Đây có vẻ là câu trả lời "cho năm 2015" mà @WillStrohl nói về.
osiris

Nghi thức SO đúng đắn ở đây là gì? Tôi có nên để lại câu trả lời được chấp nhận vì nó đúng vào thời điểm đó hay chấp nhận câu trả lời đúng cho ngày hôm nay? Chỉ muốn chủ đề dường như phổ biến này hữu ích cho khách truy cập mới.
PFranchise

@PFranchise, tôi không biết nhưng tôi nghĩ một ghi chú rõ ràng về nó có thể giúp ích. Có thể là một chỉnh sửa cho câu hỏi của bạn, vì vậy ghi chú vẫn ở nơi nhiều người có thể nhìn thấy nó.
osiris

Câu trả lời:


197

AngularJS dựa vào tên đầu vào để lộ lỗi xác nhận.

Thật không may, cho đến ngày hôm nay, không thể (không sử dụng chỉ thị tùy chỉnh) để tự động tạo tên của đầu vào. Thật vậy, kiểm tra tài liệu đầu vào chúng ta có thể thấy rằng thuộc tính name chỉ chấp nhận một chuỗi.

Để giải quyết vấn đề 'tên động', bạn cần tạo một biểu mẫu bên trong (xem dạng ng ) :

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

Cách khác là viết một chỉ thị tùy chỉnh cho việc này.

Dưới đây là jsFiddle hiển thị việc sử dụng ngForm: http://jsfiddle.net/pkozlowski_opensource/XK2ZT/2/


2
Thật tuyệt. Nhưng nó có hợp lệ để có nhiều hộp văn bản có cùng tên không?
Ian Warburton

1
Các hình thức lồng nhau không được coi là HTML stackoverflow.com/questions/379610/can-you-nest-html-forms Có phải kế hoạch góc là một sửa chữa cho điều này?
Blowsie

11
@Blowsie bạn không lồng nhau ở dạng thực ở đây, mà là ng-formcác phần tử DOM, vì vậy liên kết đến câu hỏi SO khác không liên quan ở đây.
pkozlowski.opensource

7
Tuyệt quá. Cần lưu ý rằng nếu bạn ng-repeatbị ràng buộc table trthì bạn phải sử dụng ng-form="myname"attr.
ivkremer

11
Câu trả lời này cần được chỉnh sửa: vấn đề github.com/angular/angular.js/issues/1404 đã được giải quyết kể từ AngularJS 1.3.0 (cam kết từ tháng 9 năm 2014)
tanguy_k

228

Vì câu hỏi đã được hỏi, nhóm Angular đã giải quyết vấn đề này bằng cách tạo ra các tên đầu vào một cách linh hoạt.

Với phiên bản Angular 1.3 trở lên bạn có thể làm điều này:

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

Bản giới thiệu

Angular 1.3 cũng giới thiệu ngMessages, một công cụ mạnh hơn để xác thực mẫu. Bạn có thể sử dụng kỹ thuật tương tự với 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
Điều này là hoàn hảo và dễ dàng hơn nhiều so với thực hiện một lệnh - có thể chuyển một biểu mẫu vào các thành phần và sử dụng phương pháp này. Cảm ơn bạn đời!
dinkydani

Tôi nhận thấy rằng tên biểu mẫu của bạn không thể có dấu gạch nối nếu bạn muốn nó hoạt động. Bất cứ ai biết lý do tại sao điều này?
Patrick Szalapski

@PatrickSzalapski: đó là vì tên biểu mẫu được sử dụng bởi Angular và tên biến với dấu gạch nối không phải là cú pháp hợp lệ trong Javascript. Giải pháp thay thế: <span ng-show = "vm ['my-form'] ['person_' + $ index]. $ Không hợp lệ"> Nhập tên </ span>
HoffZ

Tôi nhận thấy rằng nếu bạn loại bỏ một mục lặp đi lặp lại một cách linh hoạt, thuộc $validtính cho đầu vào sẽ không chính xácfalse
jonathanw Diesel

bạn muốn tất cả các lỗi của bạn hiển thị ở một nơi nói ở đầu biểu mẫu là gì?
mã hóabbq

13

Nếu bạn không muốn sử dụng ng-form, bạn có thể sử dụng một lệnh tùy chỉnh sẽ thay đổi thuộc tính tên của biểu mẫu. Đặt lệnh này làm thuộc tính trên cùng một phần tử với mô hình ng của bạn.

Nếu bạn đang sử dụng các chỉ thị khác kết hợp, hãy cẩn thận rằng chúng không có thuộc tính "thiết bị đầu cuối" nếu không chức năng này sẽ không thể chạy (với điều kiện là nó có mức độ ưu tiên -1).

Ví dụ: khi sử dụng lệnh này với ng-tùy chọn, bạn phải chạy một dòng khỉ này: 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;

      }
    };
});

Tôi thường thấy hữu ích khi sử dụng ng-init để đặt chỉ mục $ thành tên biến. Ví dụ:

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

Điều này thay đổi biểu thức thông thường của bạn thành:

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

Nếu bạn có nhiều lần lặp ng lồng nhau, bây giờ bạn có thể sử dụng các tên biến này thay vì $ Parent. $ Index.

Định nghĩa "thiết bị đầu cuối" và "ưu tiên" cho các chỉ thị: https://docs.angularjs.org/api/ng/service/ $ compile # directive-định nghĩa-object

Github Bình luận về nhu cầu cho ng-option monkeypatch: https://github.com/angular/angular.js/commit/9ee2cdff44e7d496774b340de816344126c457b3#commitcomment-6832095 https://twitter.com/aljohri/status/482963541520314369

CẬP NHẬT:

Bạn cũng có thể làm cho công việc này với 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
Chỉ cần làm cho nó rõ ràng, câu trả lời này không được chọn, không phải là câu trả lời tốt nhất. Nó chỉ được đăng gần 2 năm sau khi câu hỏi ban đầu được hỏi. Tôi sẽ xem xét cả câu trả lời này và câu trả lời của tomGreen ngoài câu trả lời được chọn nếu bạn gặp phải vấn đề tương tự.
PFranchise

11

Sử dụng chỉ thị ng-form bên trong thẻ mà bạn đang sử dụng lệnh ng-repeat. Sau đó, bạn có thể sử dụng phạm vi được tạo bởi chỉ thị ng-form để tham chiếu một tên chung. Ví dụ:

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

Tín dụng vào: http : //www.ben Meat.com/2013/03/angular-js-validating-form-elements-in.html


Câu trả lời được chấp nhận không làm việc cho tôi. Điều này tuy nhiên đã làm. (Tôi sử dụng Angular 2.1,14)
Jesper Tejlgaard

+1 câu trả lời này có tác dụng với tôi, hãy kiểm tra liên kết : bạn chỉ cần thêm ng-form="formName"vào thẻ có lặp lại ... nó hoạt động như một cơ duyên :)
Abdellah Alaoui

3

Đã thêm ví dụ phức tạp hơn với "xác thực tùy chỉnh" ở bên cạnh bộ điều khiển 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

Nhìn qua các giải pháp này, giải pháp do Al Johri cung cấp ở trên là gần nhất với nhu cầu của tôi, nhưng chỉ thị của anh ta ít lập trình hơn tôi muốn. Đây là phiên bản giải pháp của tôi:

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

Giải pháp này cho phép bạn chuyển một biểu thức tạo tên cho lệnh và tránh việc khóa xuống để thay thế mẫu mà anh ta đang sử dụng.

Tôi cũng gặp rắc rối ban đầu với giải pháp này vì nó không hiển thị ví dụ về việc sử dụng nó trong đánh dấu, vì vậy đây là cách tôi sử dụng nó.

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

Tôi có một ví dụ làm việc đầy đủ hơn trên github .


1

xác thực đang hoạt động với ng lặp lại nếu tôi sử dụng cú pháp sau scope.step3Form['item[107][quantity]'].$touched Tôi không biết đó là cách thực hành tốt nhất hoặc giải pháp tốt nhất, nhưng nó hoạt động

<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

Dựa trên câu trả lời của pkozlowski.opensource , tôi đã thêm một cách để có tên đầu vào động cũng hoạt động với ngMessages . Lưu ý ng-initphần trên ng-formphần tử và việc sử dụng furryName. furryNametrở thành tên biến chứa giá trị biến cho thuộc tính input' 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>

1

Quá muộn nhưng có thể giúp được ai

  1. Tạo tên duy nhất cho mọi điều khiển
  2. Xác thực bằng cách sử dụng fromname[uniquname].$error

Mã mẫu:

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

Xem bản demo làm việc tại đây


1

Nếu bạn sử dụng ng-repeat $ index hoạt động như thế này

  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>

chúng ta phải thể hiện ng-show theo mẫu ng

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

0

Có thể và đây là cách tôi làm điều tương tự với một bảng đầu vào.

bọc bàn theo hình thức như vậy

Sau đó, chỉ cần sử dụng này

Tôi có một biểu mẫu với các lệnh đa lồng nhau, tất cả đều chứa (các) đầu vào, chọn (s), v.v ... Các phần tử này đều được đặt trong các lặp lại ng và các giá trị chuỗi động.

Đây là cách sử dụng chỉ thị:

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

Lưu ý: bạn có thể thêm và lập chỉ mục cho nối chuỗi nếu bạn cần tuần tự hóa có lẽ một bảng đầu vào; đó là những gì tôi đã làm

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

Điều này sẽ xử lý nhiều tình huống mà bạn không biết biểu mẫu sẽ ở đâu. Hoặc có lẽ bạn có các biểu mẫu lồng nhau, nhưng vì một số lý do bạn muốn đính kèm tên đầu vào này vào hai biểu mẫu? Vâng, chỉ cần chuyển vào tên mẫu bạn muốn đính kèm tên đầu vào.

Những gì tôi muốn, là một cách để gán các giá trị động cho các đầu vào mà tôi sẽ không bao giờ biết, và sau đó chỉ cần gọi $ scope.myFormName. $ Hợp lệ.

Bạn có thể thêm bất cứ thứ gì bạn muốn: nhiều bảng hơn đầu vào biểu mẫu, biểu mẫu lồng nhau, bất cứ điều gì bạn muốn. Chỉ cần vượt qua tên biểu mẫu bạn muốn xác thực đầu vào. Sau đó, trên biểu mẫu gửi yêu cầu nếu $ scope.yourFormName. $ Hợp lệ


0

Điều này sẽ có được tên trong ng-repeat để đi lên riêng biệt trong xác nhận mẫu.

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

Nhưng tôi gặp khó khăn khi tìm nó trong thông báo xác thực của nó nên tôi đã phải sử dụng ng-init để lấy nó để giải quyết một biến làm khóa đối tượng.

<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

Dưới đây là một ví dụ về cách tôi làm điều đó, tôi không biết liệu đó có phải là giải pháp tốt nhất hay không, nhưng hoạt động hoàn hảo.

Đầu tiên, mã bằng HTML. Nhìn vào ng-class, nó đang gọi hàm hasError. Cũng nhìn vào khai báo tên của đầu vào. Tôi sử dụng chỉ số $ để tạo các tên đầu vào khác nhau.

<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}}"/>

Và bây giờ, đây là hàm 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

Yêu cầu của tôi hơi khác so với những câu hỏi trong câu hỏi ban đầu, nhưng hy vọng tôi có thể giúp được ai đó đang trải qua vấn đề tương tự như tôi ..

Tôi phải xác định xem một trường có bắt buộc hay không dựa trên biến phạm vi .. Vì vậy, về cơ bản tôi phải đặt ng-required="myScopeVariable"(đó là biến boolean).

<div class="align-left" ng-repeat="schema in schemas">
    <input type="text" ng-required="schema.Required" />
</div>
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.