Jak dynamicznie walidować dane wejściowe utworzone za pomocą ng-repeat, ng-show (angular)

Mam tabelę, która jest tworzona przy użyciu ng-repeat. Chcę dodać walidację do każdego elementu w tabeli. Problem polega na tym, że każda komórka wejściowa ma taką samą nazwę jak komórka nad i pod nią. Próbowałem użyć wartości {{$index}} do nazwania wejść, ale mimo, że literały łańcuchów w HTML wyglądają poprawnie, to teraz działa.

Oto Mój kod od teraz:

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

Próbowałem usunąć {{}} z indeksu, ale to też nie działa. Od teraz, właściwość validation wejścia działa poprawnie, ale Komunikat o błędzie nie jest wyświetlany.

Ktoś ma jakieś sugestie?

Edit: Oprócz wspaniałych Odpowiedzi poniżej, oto artykuł na blogu, który omawia ten problem bardziej szczegółowo: http://www.thebhwgroup.com/blog/2014/08/angularjs-html-form-design-part-2/

Author: SoEzPz, 2012-08-20

14 answers

AngularJS polega na nazwach wejściowych, aby ujawnić błędy walidacji.

Niestety, na dzień dzisiejszy nie jest możliwe (bez użycia niestandardowej dyrektywy) dynamiczne generowanie nazwy wejścia. Rzeczywiście, sprawdzając input docs widzimy, że atrybut name akceptuje tylko ciąg znaków.

Aby rozwiązać problem "nazwa dynamiczna" musisz utworzyć wewnętrzny formularz (patrz 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>

Inną alternatywą byłoby napisanie niestandardowej dyrektywy dla to.

Oto jsFiddle pokazujące użycie ngForm: http://jsfiddle.net/pkozlowski_opensource/XK2ZT/2/

 197
Author: pkozlowski.opensource,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-12 13:16:46

Ponieważ zadano pytanie, zespół Angular rozwiązał ten problem, umożliwiając dynamiczne tworzenie nazw wejściowych.

Z Angular w wersji 1.3 i nowszej możesz teraz zrobić to:

<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 wprowadził również ngMessages, potężniejsze narzędzie do walidacji formularzy. Możesz użyć tej samej techniki z 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>
 230
Author: HoffZ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-21 12:53:29

Jeśli nie chcesz używać ng-form, możesz użyć niestandardowej dyrektywy, która zmieni atrybut nazwy formularza. Umieść tę dyrektywę jako atrybut na tym samym elemencie, co twój model ng.

Jeśli używasz innych dyrektyw w połączeniu, uważaj, aby nie miały ustawionej właściwości "terminal", w przeciwnym razie ta funkcja nie będzie mogła działać (biorąc pod uwagę, że ma priorytet -1).

Na przykład, gdy używasz tej dyrektywy z ng-options, musisz uruchomić tę jedną linię 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;

      }
    };
});

Często uważam, że przydatne jest użycie ng-init, aby ustawić indeks $na nazwę zmiennej. Na przykład:

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

To zmienia Wyrażenie regularne na:

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

Jeśli masz wiele zagnieżdżonych powtórzeń ng, możesz teraz używać tych nazw zmiennych zamiast $parent.$ index.

Definicja "terminala" i "priorytetu" dla dyrektyw: https://docs.angularjs.org/api/ng/service/$compile#directive-definition-object

Komentarz Github dotyczący need for ng-option monkeypatch: https://github.com/angular/angular.js/commit/9ee2cdff44e7d496774b340de816344126c457b3#commitcomment-6832095 https://twitter.com/aljohri/status/482963541520314369

Aktualizacja:

Możesz również wykonać to zadanie za pomocą 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;
            }
          }
        }
      }
    };
});
 13
Author: Al Johri,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-28 20:54:11

Użyj dyrektywy ng-form wewnątrz znacznika, w którym używasz dyrektywy ng-repeat. Następnie można użyć zakresu utworzonego przez dyrektywę ng-form, aby odwołać się do nazwy ogólnej. Na przykład:

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

Kredyt na: http://www.benlesh.com/2013/03/angular-js-validating-form-elements-in.html

 11
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-20 19:59:18

Dodano bardziej złożony przykład z "custom validation" po stronie kontrolera 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>
 3
Author: Mikita Manko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-13 16:45:23

Patrząc na te rozwiązania, to Dostarczone przez Al Johri powyżej jest najbliższe moim potrzebom, ale jego dyrektywa była trochę mniej programowalna niż chciałem. Oto moja wersja jego rozwiązań:

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

To rozwiÄ ... zanie pozwala po prostu przekazaÄ ‡ do dyrektywy wyrażenie generatora nazw i uniknÄ ... Ä ‡ blokady do zastÄ ... pienia wzorca, ktĂłrego uĺźywaĺ'.

Miałem też początkowo problemy z tym rozwiązaniem, ponieważ nie pokazywało ono przykładu użycia go w znacznikach, więc oto jak użyłem to.

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

Mam pełniejszy działający przykład na github .

 1
Author: tomgreen98,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-04 17:57:18

Walidacja działa z ng repeat Jeśli używam następującej składni scope.step3Form['item[107][quantity]'].$touched Nie wiem, czy to najlepsza praktyka, czy najlepsze rozwiązanie, ale to działa

<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
Author: Vlad Vinnikov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-07 18:55:36

Budynek przy pkozlowskim.opensource ' s answer , dodałem sposób, aby mieć dynamiczne nazwy wejściowe, które również działają z ngMessages . Zwróć uwagę na ng-init część na elemencie ng-form i użycie furryName. furryName staje się nazwą zmiennej, która zawiera wartość zmiennej dla atrybutu 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
Author: ABCD.ca,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:53

Oto przykład, jak to robię, Nie wiem, czy to najlepsze rozwiązanie, ale działa idealnie.

Najpierw kod w HTML. Spójrz na ng-class, to wywołanie funkcji hasError. Spójrz również na deklarację nazwy wejścia. Używam indeksu $do tworzenia różnych nazw wejściowych.

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

A oto funkcja 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);
       };
 1
Author: David Martin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-01 07:13:01

Jest już za późno, ale może to pomóc każdemu

  1. Utwórz unikalną nazwę dla każdej kontroli
  2. Zatwierdź za pomocą fromname[uniquname].$error

Przykładowy kod:

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

Zobacz działające demo tutaj

 1
Author: Ali Adravi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-24 15:38:10

Jeśli Twoje użycie ng-repeat $index działa w ten sposób

  name="QTY{{$index}}"

I

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

Musimy pokazać ng-show we wzorcu ng

   <span class="alert-error" ng-show="form['QTY' + $index].$error.pattern">
   <span class="alert-error" ng-show="form['QTY' + $index].$error.required">
 1
Author: Kondal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-16 11:41:05

Jest to możliwe i oto jak robię to samo z tabelą wejść.

Owiń tabelę w taką formę

Następnie po prostu użyj tego

Mam formularz z wieloma zagnieżdżonymi dyrektywami, które wszystkie zawierają input(s), select(S), itp... Wszystkie te elementy są zamknięte w NG-repeats i dynamicznych wartościach łańcuchowych.

Oto jak korzystać z dyrektywy:

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

Uwaga: możesz dodać i indeksować do konkatenacji ciągu znaków, jeśli chcesz serializować tabelę wejścia; czyli to co zrobiłem.

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

To powinno obsłużyć wiele sytuacji, w których po prostu nie wiesz, gdzie będzie formularz. A może masz zagnieżdżone formularze, ale z jakiegoś powodu chcesz dołączyć tę nazwę wejściową do dwóch formularzy? Po prostu podaj nazwę formularza, do której chcesz dołączyć nazwę wejściową.

To, czego chciałem, to sposób na przypisanie dynamicznych wartości wejściom, których nigdy nie poznam, a następnie po prostu wywołanie $scope.myFormName.$ valid.

Możesz dodać wszystko, co chcesz: więcej tabel więcej wejść formularzy, zagnieżdżonych formularzy, cokolwiek chcesz. Po prostu podaj nazwę formularza, z którą chcesz zweryfikować dane wejściowe. Następnie w formularzu wyślij pytanie, czy $ zakres.yourFormName.$valid

 0
Author: SoEzPz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-22 01:20:19

Spowoduje to, że nazwa w ng-repeat pojawi się oddzielnie w walidacji formularza.

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

Ale miałem problem z odnalezieniem go w wiadomości walidacji, więc musiałem użyć ng-init, aby rozwiązać zmienną jako klucz obiektu.

<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
Author: Andrew Clavin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-04 13:38:52

Moje wymagania były nieco inne niż te zadane na pierwotnym pytaniu, ale mam nadzieję, że mogę pomóc komuś, kto przechodzi przez ten sam problem, co ja..

Musiałem zdefiniować, czy pole jest wymagane, czy nie na podstawie zmiennej zakresu.. Więc w zasadzie musiałem ustawić ng-required="myScopeVariable" (która jest zmienną logiczną).

<div class="align-left" ng-repeat="schema in schemas">
    <input type="text" ng-required="schema.Required" />
</div>
 0
Author: Bartho Bernsmann,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-28 01:57:15