So validieren Sie dynamisch erstellte Eingaben mit ng-repeat, ng-show (eckig)

167

Ich habe eine Tabelle, die mit ng-repeat erstellt wird. Ich möchte jedem Element in der Tabelle eine Validierung hinzufügen. Das Problem ist, dass jede Eingabezelle denselben Namen hat wie die darüber und darunter liegende Zelle. Ich habe versucht, den {{$index}}Wert zum Benennen der Eingaben zu verwenden, aber obwohl die Zeichenfolgenliterale in HTML korrekt erscheinen, funktioniert es jetzt.

Hier ist mein Code ab sofort:

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

Ich habe versucht, den {{}}Index zu entfernen , aber das funktioniert auch nicht. Ab sofort funktioniert die Validierungseigenschaft der Eingabe ordnungsgemäß, die Fehlermeldung wird jedoch nicht angezeigt.

Hat jemand irgendwelche Vorschläge?

Bearbeiten: Zusätzlich zu den großartigen Antworten unten finden Sie hier einen Blog-Artikel, der dieses Problem ausführlicher behandelt: http://www.thebhwgroup.com/blog/2014/08/angularjs-html-form-design-part-2 /.

PFranchise
quelle
4
Für diejenigen, die dies 2015 lesen ... ist die am besten gewählte Antwort NICHT mehr die richtige. Schau tiefer. :)
Will Strohl
Dies scheint die Antwort "für 2015" zu sein, über die @WillStrohl spricht.
Osiris
Was ist hier die richtige SO-Etikette? Sollte ich die akzeptierte Antwort verlassen, da sie zu diesem Zeitpunkt korrekt war, oder die richtige Antwort für heute akzeptieren? Ich möchte nur, dass dieser scheinbar beliebte Thread neuen Besuchern hilft.
PFranchise
@PFranchise, ich weiß es nicht, aber ich denke, eine sichtbare Notiz darüber könnte helfen. Vielleicht als Bearbeitung Ihrer Frage, damit die Notiz dort bleibt, wo mehr Leute sie sehen können.
Osiris

Antworten:

197

AngularJS verwendet Eingabenamen, um Validierungsfehler aufzudecken.

Leider ist es bis heute nicht möglich (ohne Verwendung einer benutzerdefinierten Direktive), einen Namen einer Eingabe dynamisch zu generieren. Wenn wir die Eingabedokumente überprüfen , können wir feststellen, dass das Namensattribut nur eine Zeichenfolge akzeptiert.

Um das Problem des dynamischen Namens zu lösen , müssen Sie ein inneres Formular erstellen (siehe ng-Formular ) :

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

Die andere Alternative wäre, eine benutzerdefinierte Direktive dafür zu schreiben.

Hier ist die jsFiddle, die die Verwendung der ngForm zeigt: http://jsfiddle.net/pkozlowski_opensource/XK2ZT/2/

pkozlowski.opensource
quelle
2
Das ist großartig. Aber ist es in HTML gültig, mehrere Textfelder mit demselben Namen zu haben?
Ian Warburton
1
Das Verschachteln von Formularen wird nicht als gültiges HTML angesehen. Stackoverflow.com/questions/379610/can-you-nest-html-forms Ist die Winkelplanung eine Lösung dafür?
Blowsie
11
@Blowsie Sie verschachteln hier keine echte Form, sondern ng-formDOM-Elemente, daher ist der Link zur anderen SO-Frage hier nicht relevant.
pkozlowski.opensource
7
Toll. Es sollte beachtet werden, dass Sie attr verwenden müssen , wenn Sie ng-repeatgebunden table trsind ng-form="myname".
ivkremer
11
Diese Antwort sollte bearbeitet werden: Das Problem github.com/angular/angular.js/issues/1404 wurde seit AngularJS 1.3.0 (Commit ab September 2014)
behoben
228

Seit die Frage gestellt wurde, hat das Angular-Team dieses Problem gelöst, indem es die dynamische Erstellung von Eingabenamen ermöglicht hat.

Mit Angular Version 1.3 und höher können Sie dies jetzt tun:

<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 führte auch ngMessages ein, ein leistungsfähigeres Tool zur Formularvalidierung. Sie können dieselbe Technik mit ngMessages verwenden:

<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>
HoffZ
quelle
2
Dies ist perfekt und viel einfacher als eine Direktive - kann ein Formular an Komponenten übergeben und diese Methode verwenden. Danke Kumpel!
Dinkydani
Ich habe festgestellt, dass Ihr Formularname keine Bindestriche enthalten kann, wenn dies funktionieren soll. Weiß jemand warum das so ist?
Patrick Szalapski
@PatrickSzalapski: Dies liegt daran, dass der Formularname von Angular verwendet wird und Variablennamen mit Bindestrichen in Javascript keine gültige Syntax sind. Problemumgehung: <span ng-show = "vm ['my-form'] ['person_' + $ index]. $ Invalid"> Geben Sie einen Namen ein </ span>
HoffZ
Ich habe festgestellt, dass, wenn Sie ein wiederholtes Element dynamisch entfernen, die $validEigenschaft für die Eingabe falsch wirdfalse
Jonathanwiesel
Was möchten Sie, dass alle Ihre Fehler an einer Stelle oben im Formular angezeigt werden?
Codingbbq
13

Wenn Sie ng-form nicht verwenden möchten, können Sie eine benutzerdefinierte Direktive verwenden, die das Namensattribut des Formulars ändert. Platzieren Sie diese Direktive als Attribut auf demselben Element wie Ihr ng-Modell.

Wenn Sie andere Anweisungen in Verbindung verwenden, achten Sie darauf, dass für sie die Eigenschaft "terminal" nicht festgelegt ist, da diese Funktion sonst nicht ausgeführt werden kann (vorausgesetzt, sie hat die Priorität -1).

Wenn Sie diese Anweisung beispielsweise mit ng-Optionen verwenden, müssen Sie diesen einzeiligen Monkeypatch ausführen: 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;

      }
    };
});

Ich finde es oft nützlich, ng-init zu verwenden, um den $ index auf einen Variablennamen zu setzen. Beispielsweise:

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

Dies ändert Ihren regulären Ausdruck in:

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

Wenn Sie mehrere verschachtelte ng-Wiederholungen haben, können Sie jetzt diese Variablennamen anstelle von $ parent. $ Index verwenden.

Definition von "Terminal" und "Priorität" für Direktiven: https://docs.angularjs.org/api/ng/service/ $ compile # directive-definition-object

Github-Kommentar zur Notwendigkeit eines Monkeypatch mit ng-Option: https://github.com/angular/angular.js/commit/9ee2cdff44e7d496774b340de816344126c457b3#commitcomment-6832095 https://twitter.com/aljohri/status/482935

AKTUALISIEREN:

Sie können diese Funktion auch mit ng-form ausführen.

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;
            }
          }
        }
      }
    };
});
Al Johri
quelle
3
Nur um klar zu stellen, dass diese Antwort nicht ausgewählt wurde, bedeutet dies nicht, dass sie nicht die beste Antwort ist. Es wurde gerade fast 2 Jahre nach der ursprünglichen Frage gestellt. Ich würde sowohl diese als auch die von tomGreen zusätzlich zu der ausgewählten Antwort berücksichtigen, wenn Sie auf dasselbe Problem stoßen.
PFranchise
11

Verwenden Sie die Anweisung ng-form innerhalb des Tags, in dem Sie die Anweisung ng-repeat verwenden. Sie können dann den von der Direktive ng-form erstellten Bereich verwenden, um auf einen generischen Namen zu verweisen. Beispielsweise:

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

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


quelle
Die akzeptierte Antwort hat bei mir nicht funktioniert. Dieser tat es jedoch. (Ich benutze Angular 2.1.14)
Jesper Tejlgaard
+1 Diese Antwort hat bei mir funktioniert. Überprüfen Sie den Link : Sie müssen nur hinzufügenng-form="formName" zu dem Tag , das ng-repeat hat ... es hat wie ein Zauber funktioniert :)
Abdellah Alaoui
3

Komplexeres Beispiel mit "benutzerdefinierter Validierung" auf der Seite des Controllers hinzugefügt 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>
Mikita Manko
quelle
1

Wenn ich mir diese Lösungen anschaue, ist die von Al Johri oben bereitgestellte meinen Bedürfnissen am nächsten, aber seine Anweisung war etwas weniger programmierbar als ich wollte. Hier ist meine Version seiner Lösungen:

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

Mit dieser Lösung können Sie einfach einen Namensgeneratorausdruck an die Direktive übergeben und die Sperrung der von ihm verwendeten Mustersubstitution vermeiden.

Ich hatte anfangs auch Probleme mit dieser Lösung, da sie kein Beispiel für die Verwendung in Markups zeigte. Deshalb habe ich sie hier verwendet.

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

Ich habe ein vollständigeres Arbeitsbeispiel für Github .

tomgreen98
quelle
1

Die Validierung funktioniert mit ng repeat, wenn ich die folgende Syntax verwende. scope.step3Form['item[107][quantity]'].$touched Ich weiß nicht, ob dies eine bewährte Methode oder die beste Lösung ist, aber sie funktioniert

<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>
Vlad Vinnikov
quelle
1

Aufbauend auf der Antwort von pkozlowski.opensource habe ich eine Möglichkeit hinzugefügt, dynamische Eingabenamen zu verwenden, die auch mit ngMessages funktionieren . Beachten Sie den ng-initTeil auf dem ng-formElement und die Verwendung von furryName. furryNamewird zum Variablennamen, der den Variablenwert für das Attribut input' enthält 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>
ABCD.ca
quelle
1

Es ist zu spät, aber vielleicht kann es jedem helfen

  1. Erstellen Sie für jedes Steuerelement einen eindeutigen Namen
  2. Validieren Sie mit fromname[uniquname].$error

Beispielcode:

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

Siehe Arbeits Demo hier

Ali Adravi
quelle
1

Wenn Ihr ng-repeat $ index so funktioniert

  name="QTY{{$index}}"

und

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

wir müssen die ng-show im ng-muster zeigen

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

Es ist möglich und hier ist, wie ich dasselbe mit einer Tabelle von Eingaben mache.

Wickeln Sie den Tisch in eine Form wie diese

Dann benutze einfach das

Ich habe ein Formular mit mehrfach verschachtelten Anweisungen, die alle Eingaben, Auswahlen usw. enthalten. Diese Elemente sind alle in ng-Wiederholungen und dynamischen Zeichenfolgenwerten enthalten.

So verwenden Sie die Richtlinie:

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

Hinweis: Sie können die Zeichenfolgenverkettung hinzufügen und indizieren, wenn Sie möglicherweise eine Tabelle mit Eingaben serialisieren müssen. was ich getan habe.

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

Dies sollte viele Situationen bewältigen, in denen Sie einfach nicht wissen, wo sich das Formular befindet. Oder haben Sie verschachtelte Formulare, möchten diesen Eingabenamen jedoch aus irgendeinem Grund an zwei Formulare anhängen? Geben Sie einfach den Formularnamen ein, an den Sie den Eingabenamen anhängen möchten.

Was ich wollte, war eine Möglichkeit, Eingaben, die ich nie erfahren werde, dynamische Werte zuzuweisen und dann einfach $ scope.myFormName. $ Valid aufzurufen.

Sie können alles hinzufügen, was Sie möchten: mehr Tabellen, mehr Formulareingaben, verschachtelte Formulare, was immer Sie wollen. Übergeben Sie einfach den Formularnamen, anhand dessen Sie die Eingaben überprüfen möchten. Fragen Sie dann beim Senden des Formulars, ob $ scope.yourFormName. $ Gültig ist

SoEzPz
quelle
0

Dadurch wird der Name in der ng-Wiederholung separat in der Formularvalidierung angezeigt.

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

Ich hatte jedoch Probleme, es in der Validierungsnachricht nachzuschlagen, sodass ich ein ng-init verwenden musste, um eine Variable als Objektschlüssel aufzulösen.

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

Andrew Clavin
quelle
0

Hier ein Beispiel, wie ich das mache, ich weiß nicht, ob es die beste Lösung ist, aber es funktioniert perfekt.

Zuerst Code in HTML. Schauen Sie sich die ng-Klasse an, sie ruft die Funktion hasError auf. Beachten Sie auch die Namensdeklaration der Eingabe. Ich benutze den $ Index, um verschiedene Eingabenamen zu erstellen.

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

Und jetzt ist hier die hasError-Funktion:

$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);
       };
David Martin
quelle
0

Meine Anforderungen waren etwas anders als die, die in der ursprünglichen Frage gestellt wurden, aber ich hoffe, ich kann jemandem helfen, der das gleiche Problem hat wie ich.

Ich musste definieren, ob ein Feld erforderlich war oder nicht, basierend auf einer Bereichsvariablen. Also musste ich im Grunde setzen ng-required="myScopeVariable"(was eine boolesche Variable ist).

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