Come validare input creati dinamicamente usando ng-repeat, ng-show (angolare)


167

Ho una tabella creata usando ng-repeat. Voglio aggiungere la convalida a ciascun elemento della tabella. Il problema è che ogni cella di input ha lo stesso nome della cella sopra e sotto di essa. Ho tentato di utilizzare il {{$index}}valore per nominare gli input, ma nonostante i valori letterali delle stringhe in HTML appaiano corretti, ora funziona.

Ecco il mio codice a partire da ora:

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

Ho provato a rimuovere l' {{}}indice, ma neanche quello funziona. A partire da ora, la proprietà di convalida dell'input funziona correttamente, ma il messaggio di errore non viene visualizzato.

Qualcuno ha qualche suggerimento?

Modifica: oltre alle ottime risposte di seguito, ecco un articolo di blog che tratta questo problema in modo più dettagliato: http://www.thebhwgroup.com/blog/2014/08/angularjs-html-form-design-part-2 /


4
Per coloro che leggono questo nel 2015 ... la risposta più votata NON è più quella corretta. Guarda in basso. :)
Will Strohl,

Questa sembra essere la risposta "per il 2015" di cui parla @WillStrohl.
Osiride,

Qual è la corretta etichetta SO qui? Devo lasciare la risposta accettata poiché era corretta al momento o accettare la risposta corretta per oggi? Voglio solo che questo thread apparentemente popolare sia utile per i nuovi visitatori.
PFranchise,

@PFranchise, non lo so, ma penso che una nota visibile a riguardo possa aiutare. Forse come modifica alla tua domanda, quindi la nota rimane dove più persone possono vederla.
Osiride,

Risposte:


197

AngularJS si affida ai nomi di input per esporre errori di validazione.

Sfortunatamente, ad oggi, non è possibile (senza utilizzare una direttiva personalizzata) generare dinamicamente un nome di un input. In effetti, controllando i documenti di input possiamo vedere che l'attributo name accetta solo una stringa.

Per risolvere il problema del "nome dinamico" devi creare un modulo interno (vedi 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>

L'altra alternativa sarebbe quella di scrivere una direttiva personalizzata per questo.

Ecco il jsFiddle che mostra l'uso di ngForm: http://jsfiddle.net/pkozlowski_opensource/XK2ZT/2/


2
È fantastico. Ma è html valido avere più caselle di testo con lo stesso nome?
Ian Warburton,

1
I moduli di nidificazione non sono considerati HTML stackoverflow.com/questions/379610/can-you-nest-html-forms HTML validi La pianificazione angolare è una soluzione per questo?
Blowsie,

11
@Blowsie si sta non nidificazione forma reale qui, ma piuttosto ng-formDOM elementi, in modo che il collegamento per l'altra domanda SO non è rilevante qui.
pkozlowski.opensource,

7
Grande. Va notato che se si ng-repeatè vincolati, table trè necessario utilizzare ng-form="myname"attr.
Ivkremer,

11
Questa risposta dovrebbe essere modificata: il problema github.com/angular/angular.js/issues/1404 è stato risolto da AngularJS 1.3.0 (commit da settembre 2014)
tanguy_k

228

Da quando è stata posta la domanda, il team di Angular ha risolto questo problema rendendo possibile la creazione dinamica di nomi di input.

Con Angular versione 1.3 e successive ora puoi farlo:

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

dimostrazione

Angular 1.3 ha inoltre introdotto ngMessages, uno strumento più potente per la convalida dei moduli. Puoi usare la stessa tecnica con 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
Questo è perfetto e molto più semplice rispetto a una direttiva: può passare un modulo ai componenti e utilizzare questo metodo. Grazie compagno!
Dinkydani,

Ho notato che il nome del tuo modulo non può avere trattini se vuoi che funzioni. Qualcuno sa perché questo è?
Patrick Szalapski,

@PatrickSzalapski: è perché il nome del modulo viene utilizzato dai nomi angolari e variabili con trattini non è una sintassi valida in Javascript. Soluzione: <span ng-show = "vm ['my-form'] ['person_' + $ index]. $ Invalid"> Inserisci un nome </span>
HoffZ

Ho notato che se si rimuove un elemento ripetuto in modo dinamico, la $validproprietà per l'input viene erroneamentefalse
jonathanwiesel

cosa vuoi che vengano visualizzati tutti i tuoi errori in un unico posto nella parte superiore del modulo?
codingbbq,

13

Se non si desidera utilizzare ng-form, è possibile utilizzare una direttiva personalizzata che cambierà l'attributo name del modulo. Posiziona questa direttiva come attributo sullo stesso elemento del tuo modello ng.

Se si utilizzano congiuntamente altre direttive, fare attenzione a non impostare la proprietà "terminal", altrimenti questa funzione non sarà in grado di eseguire (dato che ha una priorità di -1).

Ad esempio, quando si utilizza questa direttiva con ng-options, è necessario eseguire questo monkeypatch a una riga: 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;

      }
    };
});

Trovo spesso utile usare ng-init per impostare $ index su un nome di variabile. Per esempio:

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

Questo cambia la tua espressione regolare in:

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

Se hai più ng-ripetizioni nidificate, ora puoi utilizzare questi nomi di variabili anziché $ parent. $ Index.

Definizione di "terminale" e "priorità" per le direttive: https://docs.angularjs.org/api/ng/service/ $ compile # direttiva-definizione-oggetto

Commento di Github in merito alla necessità del nke -atch monkeypatch: https://github.com/angular/angular.js/commit/9ee2cdff44e7d496774b340de816344126c457b3#commitcomment-6832095 https://twitter.com/aljohri/status/48296354143

AGGIORNARE:

Puoi anche farlo funzionare con 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
Giusto per chiarire che questa risposta non viene selezionata, non è indicativa del fatto che non è la risposta migliore. È stato appena pubblicato quasi 2 anni dopo la domanda iniziale. Prenderei in considerazione sia questa risposta che quella di TomGreen oltre alla risposta selezionata se riscontri questo stesso problema.
PFranchise,

11

Usa la direttiva ng-form all'interno del tag in cui stai usando la direttiva ng-repeat. È quindi possibile utilizzare l'ambito creato dalla direttiva ng-form per fare riferimento a un nome generico. Per esempio:

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

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


La risposta accettata non ha funzionato per me. Questo però lo ha fatto. (Uso Angular 2.1.14)
Jesper Tejlgaard

+1 questa risposta ha funzionato per me controlla il link : devi solo aggiungere ng-form="formName"al tag che ha ng-repeat ... ha funzionato come un fascino :)
Abdellah Alaoui

3

Aggiunto esempio più complesso con "validazione personalizzata" sul lato del controller 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

Esaminando queste soluzioni, quella fornita da Al Johri sopra è la più vicina alle mie esigenze, ma la sua direttiva era un po 'meno programmabile di quanto volessi. Ecco la mia versione delle sue soluzioni:

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

Questa soluzione consente semplicemente di passare un'espressione del generatore di nomi alla direttiva ed evita il blocco fino alla sostituzione del modello che stava utilizzando.

Inizialmente ho anche avuto problemi con questa soluzione poiché non mostrava un esempio di utilizzo nel markup, quindi ecco come l'ho usato.

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

Ho un esempio di lavoro più completo su github .


1

la validazione funziona con ng repeat se uso la sintassi seguente scope.step3Form['item[107][quantity]'].$touched Non so che sia una buona pratica o la migliore soluzione, ma funziona

<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

Sulla base della risposta di pkozlowski.opensource , ho aggiunto un modo per avere nomi di input dinamici che funzionano anche con ngMessages . Nota la ng-initparte ng-formsull'elemento e l'uso di furryName. furryNamediventa il nome della variabile che contiene il valore della variabile per la input's nameattributo.

<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

È troppo tardi, ma potrebbe essere che può aiutare chiunque

  1. Crea un nome univoco per ogni controllo
  2. Convalida utilizzando fromname[uniquname].$error

Codice di esempio:

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

Guarda la demo funzionante qui


1

Se stai usando ng-repeat $ index funziona in questo modo

  name="QTY{{$index}}"

e

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

dobbiamo mostrare ng-show in ng-pattern

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

0

È possibile ed ecco come faccio la stessa cosa con una tabella di input.

avvolgere il tavolo in una forma simile

Quindi usa questo

Ho un modulo con direttive multi-nidificate che contengono tutti input (s), select (s), ecc ... Questi elementi sono tutti racchiusi in ng-repeats e valori di stringa dinamici.

Ecco come utilizzare la direttiva:

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

Nota: è possibile aggiungere e indicizzare alla concatenazione di stringhe se è necessario serializzare forse una tabella di input; che è quello che ho fatto.

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

Questo dovrebbe gestire molte situazioni in cui semplicemente non sai dove sarà il modulo. O forse hai moduli nidificati, ma per qualche motivo vuoi associare questo nome di input a due moduli? Bene, basta inserire il nome del modulo a cui si desidera allegare il nome di input.

Quello che volevo era un modo per assegnare valori dinamici a input che non avrei mai saputo, e quindi chiamare $ scope.myFormName. $ Valid.

Puoi aggiungere qualsiasi altra cosa tu voglia: più tabelle più input modulo, moduli nidificati, qualunque cosa tu voglia. Basta passare il nome del modulo su cui si desidera convalidare gli input. Quindi, sul modulo invia chiedi se $ scope.yourFormName. $ Valido


0

Questo otterrà il nome in ng-repeat per apparire separato nella validazione del modulo.

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

Ma ho avuto difficoltà a farlo cercare nel suo messaggio di convalida, quindi ho dovuto usare un ng-init per farlo risolvere una variabile come chiave dell'oggetto.

<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

Ecco un esempio di come lo faccio, non so se sia la soluzione migliore, ma funziona perfettamente.

Innanzitutto, codice in HTML. Guarda ng-class, sta chiamando la funzione hasError. Guarda anche la dichiarazione del nome dell'input. Uso l'indice $ per creare nomi di input diversi.

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

E ora, ecco la funzione 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

Le mie esigenze erano un po 'diverse da quelle poste sulla domanda originale, ma spero di poter aiutare qualcuno che sta attraversando lo stesso problema che stavo ..

Ho dovuto definire se un campo fosse richiesto o meno in base a una variabile scope. Quindi, in sostanza, ho dovuto impostare ng-required="myScopeVariable"(che è una variabile booleana).

<div class="align-left" ng-repeat="schema in schemas">
    <input type="text" ng-required="schema.Required" />
</div>
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.