Как проверить входные данные, динамически созданные с использованием ng-repeat, ng-show (angular)


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>

Я попытался удалить {{}}из индекса, но это тоже не работает. На данный момент свойство проверки ввода работает правильно, но сообщение об ошибке не отображается.

У кого-нибудь есть предложения?

Изменить: В дополнение к отличным ответам ниже, в статье блога, которая более подробно освещает эту проблему: http://www.thebhwgroup.com/blog/2014/08/angularjs-html-form-design-part-2 /


4
Для тех, кто читает это в 2015 году ... лучший голос больше не является правильным. Посмотри ниже. :)
Уилл Штрол

Это кажется , что быть «на 2015 год» ответ @WillStrohl говорит о.
Осирис

Что такое правильный этикет здесь? Должен ли я оставить принятый ответ, так как он был правильным в то время, или принять правильный ответ на сегодня? Просто хочу, чтобы эта, казалось бы, популярная тема была полезной для новых посетителей.
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 иметь несколько текстовых полей с одинаковыми именами?
Ян Варбертон

1
Вложенные формы не считаются действительными HTML stackoverflow.com/questions/379610/can-you-nest-html-forms Является ли угловое планирование исправлением для этого?
Blowie

11
@Blowie вы не вкладываете здесь реальную форму, а скорее в ng-formэлементы DOM, поэтому ссылка на другой вопрос SO здесь не актуальна.
pkozlowski.opensource

7
Отлично. Следует заметить, что если вы ng-repeatсвязаны, table trто вы должны использоватьng-form="myname" attr.
ivkremer

11
Этот ответ следует отредактировать: проблема github.com/angular/angular.js/issues/1404 была решена начиная с AngularJS 1.3.0 (фиксация с сентября 2014 г.)
tanguy_k

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
Это идеально и намного проще, чем делать директивы - можно передать форму в компоненты и использовать этот метод. Спасибо друг!
Динкидани

Я заметил, что в имени вашей формы не должно быть дефисов, если вы хотите, чтобы это работало. Кто-нибудь знает, почему это?
Патрик Салапски

@PatrickSzalapski: это потому, что имя формы используется Angular, а имена переменных с дефисами не являются допустимым синтаксисом в Javascript. Обходной путь: <span ng-show = "vm ['my-form'] ['person_' + $ index]. $ Invalid"> Введите имя </ span>
HoffZ

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

Что вы хотите, чтобы все ваши ошибки отображались в одном месте, например, в верхней части формы?
codingbbq

13

Если вы не хотите использовать ng-form, вы можете использовать пользовательскую директиву, которая изменит атрибут name формы. Поместите эту директиву как атрибут в тот же элемент, что и ваша ng-модель.

Если вы используете совместно другие директивы, будьте осторожны, чтобы у них не было установлено свойство «терминала», иначе эта функция не сможет работать (учитывая, что она имеет приоритет -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-повторов, теперь вы можете использовать эти имена переменных вместо $ parent. $ Index.

Определение «терминала» и «приоритета» для директив: https://docs.angularjs.org/api/ng/service/ $ compile # directive-definition-object

Комментарий Github относительно необходимости в ng-option monkeypatch: https://github.com/angular/angular.js/commit/9ee2cdff44e7d496774b340de816344126c457b3#commitcomment-6832095 https://twitter.com/aljohri/status/4829636915

ОБНОВИТЬ:

Вы также можете сделать это с помощью 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-form внутри тега, в котором вы используете директиву ng-repeat. Затем вы можете использовать область, созданную директивой 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-repeat ... это сработало как шарм :)
Абделла Алауи

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

проверка работает с повторением ng, если я использую следующий синтаксис, 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становится именем переменной , которая содержит значение переменной для input«s 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-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>

мы должны показать нг-шоу в нг-шаблоне

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

Это должно обрабатывать многие ситуации, когда вы просто не знаете, где будет форма. Или, возможно, у вас есть вложенные формы, но по какой-то причине вы хотите прикрепить это входное имя к двум формам? Ну, просто передайте имя формы, к которой вы хотите присоединить имя ввода.

То, что я хотел, - это способ назначать динамические значения входным данным, которые я никогда не узнаю, а затем просто вызывать $ scope.myFormName. $ Valid.

Вы можете добавить все, что пожелаете: больше таблиц, больше форм ввода, вложенные формы, все, что вы хотите. Просто передайте имя формы, с которой вы хотите проверить вводимые данные. Затем при отправке формы спросите, действителен ли $ scope.yourFormName. $


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-класс, он вызывает функцию 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.