Як перевірити динамічно створені введення, використовуючи ng-повтор, ng-show (кутовий)


167

У мене є таблиця, яка створена за допомогою ng-повтору. Я хочу додати перевірку до кожного елемента таблиці. Проблема полягає в тому, що кожна вхідна комірка має те саме ім'я, що і комірка над і під нею. Я намагався використати {{$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 /


4
Для тих, хто читає це у 2015 році ... відповідь, що голосує вгорі, вже не є правильною. Подивіться нижче. :)
Буде Стролл

Це здається , що бути «на 2015 рік» відповідь @WillStrohl говорить о.
osiris

Який тут правильний етикет SO? Чи варто залишити прийняту відповідь, оскільки вона була правильною на той момент, або прийняти правильну відповідь на сьогодні? Просто хочу, щоб ця, здавалося б, популярна нитка була корисною для нових відвідувачів.
PFranchise

@PFranchise, я не знаю, але, думаю, помітна примітка про це могла б допомогти. Можливо, як редагування вашого запитання, тому примітка залишається там, де її можуть бачити більше людей.
osiris

Відповіді:


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>

Іншою альтернативою було б написати власну директиву для цього.

Ось jsFiddle, що показує використання ngForm: http://jsfiddle.net/pkozlowski_opensource/XK2ZT/2/


2
Це чудово. Але чи дійсно html мати кілька текстових полів з однаковою назвою?
Ian Warburton

1
Форми вставки не вважаються дійсними HTML stackoverflow.com/questions/379610/can-you-nest-html-forms Чи є кутове планування виправленням цього?
Blowsie

11
@Blowsie ви тут не вкладаєте реальну форму, а скоріше ng-formелементи DOM, тому посилання на інше питання ТА тут не доречне.
pkozlowski.opensource

7
Чудово. Слід зауважити, що якщо ваш ng-repeatприв’язаний, table trто вам доведеться використовувати ng-form="myname"attr.
ivkremer

11
Цю відповідь слід відредагувати: питання github.com/angular/angular.js/isissue/1404 вирішено з моменту AngularJS 1.3.0 ( ввести з вересня 2014 р.)
приєднано

228

Оскільки питання було задано, команда 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
Це ідеально і набагато простіше, ніж виконувати директиву - можна передати форму в компоненти і використовувати цей метод. Дякую, друже!
динкідані

Я помітив, що у вашій формі не може бути дефісів, якщо ви хочете, щоб це працювало. Хтось знає, чому це?
Патрік Шалапський

@PatrickSzalapski: це тому, що назва форми використовується угловими і назви змінних з дефісами, невірний синтаксис у Javascript. Вирішення: <span ng-show = "vm ['my-form'] ['person_' + $ index]. $ Invalid"> Введіть ім'я </span>
HoffZ

Я помітив, що якщо динамічно вилучити повторний елемент, $validвластивість для введення отримується неправильноfalse
jonathanwiesel

що ви хочете, щоб усі ваші помилки відображалися в одному місці, наприклад у верхній частині форми?
codingbbq

13

Якщо ви не хочете використовувати ng-form, ви можете використовувати спеціальну директиву, яка змінить атрибут імені форми. Розмістіть цю директиву як атрибут на тому ж елементі, що і ваша ng-модель.

Якщо ви використовуєте інші директиви спільно, будьте обережні, щоб у них не було встановлено властивість "terminal", інакше ця функція не зможе запускатися (враховуючи, що пріоритет має -1).

Наприклад, використовуючи цю директиву з ng-параметрами, ви повинні запустити цю маніпуляцію з одного рядка: 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 для встановлення імені змінної $. Наприклад:

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

Це змінює ваш регулярний вираз на:

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

Якщо у вас є кілька вкладених ng-повторів, тепер ви можете використовувати ці імена змінних замість $ parent. $ Index.

Визначення термінів та "пріоритету" для директив: https://docs.angularjs.org/api/ng/service/ $ compile # директива-визначення-об'єкт

Коментар Github щодо потреби в ng-option monkeypatch: https://github.com/angular/angular.js/commit/9ee2cdff44e7d496774b340de816344126c457b3#commitcomment-6832095 https://twitter.com/aljohri/status/4829635415

ОНОВЛЕННЯ:

Ви також можете зробити цю роботу за допомогою ng-форми.

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
Просто для того, щоб було зрозуміло, що ця відповідь не обрана, це не означає, що вона не є найкращою відповіддю. Він був опублікований майже через 2 роки після того, як питання було спочатку задано. Я б розглядав як цю відповідь, так і tomGreen на додаток до обраної відповіді, якщо ви зіткнетесь із цим самим питанням.
PFranchise

11

Використовуйте директиву ng-форми всередині тегу, у якому ви використовуєте директиву ng-повтор. Потім можна використовувати область, створену директивою 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

+1 ця відповідь працювала для мене. Перевір посилання : потрібно просто додати ng-form="formName"тег, у якому є ng-повторення ... це спрацювало як принадність :)
Абделла Алауї

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

Переглядаючи ці рішення, той, який надав Ел Джонрі вище, є найближчим до моїх потреб, але його директива була трохи менш програмованою, ніж я хотів. Ось моя версія його рішень:

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

перевірка працює з повторенням, якщо я використовую наступний синтаксис, 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елемента та використання furryName. furryNameстає ім'ям змінної, що містить значення змінної для атрибута inputs 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

Це вже пізно, але, можливо, це може допомогти будь-кому

  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-повторення $ 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-show у ng-шаблоні

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

0

Можна і ось як я те ж саме роблю з таблицею входів.

загорніть стіл у таку форму

Тоді просто використовуйте це

У мене є форма з багатовкладеними директивами, які містять усі введення (и), вибірка (и) тощо. Ці елементи укладені у ng-повторах та значеннях динамічного рядка.

Ось як використовувати директиву:

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

Це має вирішувати багато ситуацій, коли ви просто не знаєте, де буде форма. Або, можливо, у вас вкладені форми, але ви чомусь хочете приєднати це вхідне ім'я до двох форм вгору? Ну просто введіть назву форми, до якої потрібно вкласти ім'я вводу.

Те, що я хотів, - це спосіб присвоїти динамічні значення вхідним даних, про які я ніколи не дізнаюся, а потім просто зателефонувати $ Domain.myFormName. $ Valid.

Ви можете додати все, що завгодно: більше таблиць, більше входів форм, вкладених форм, що завгодно. Просто введіть назву форми, проти якої потрібно перевірити дані. Потім надішліть форму, запитайте, чи дійсний $ range.yourFormName. $


0

Це отримає ім'я в ng-повторі, щоб вийти окремо у валідації форми.

<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-клас, він викликає функцію 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>
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.