web-dev-qa-db-fra.com

Comment valider des entrées créées dynamiquement à l'aide de ng-repeat, ng-show (angular)

J'ai une table qui est créée en utilisant ng-repeat. Je veux ajouter une validation à chaque élément du tableau. Le problème est que chaque cellule d'entrée a le même nom que la cellule au-dessus et au-dessous. J'ai essayé d'utiliser la valeur {{$index}} pour nommer les entrées, mais malgré l'affichage correct des littéraux de chaîne en HTML, cela fonctionne maintenant.

Voici mon code à partir de maintenant:

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

J'ai essayé de supprimer le {{}} de l'index, mais cela ne fonctionne pas non plus. À partir de maintenant, la propriété de validation de l'entrée fonctionne correctement, mais le message d'erreur ne s'affiche pas.

Quelqu'un a des suggestions?

Edit: En plus des bonnes réponses ci-dessous, voici un article de blog qui couvre ce problème plus en détail: http: // www .thebhwgroup.com/blog/2014/08/angularjs-html-form-design-part-2 /

166
PFranchise

AngularJS s'appuie sur les noms d'entrée pour exposer les erreurs de validation.

Malheureusement, à ce jour, il n'est pas possible (sans utiliser de directive personnalisée) de générer dynamiquement le nom d'une entrée. En effet, en vérifiant docs en entrée , nous pouvons voir que l'attribut name accepte uniquement une chaîne.

Pour résoudre le problème du 'nom dynamique' , vous devez créer un formulaire interne (voir 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'autre alternative serait d'écrire une directive personnalisée pour cela.

Voici le jsFiddle montrant l'utilisation du ngForm: http://jsfiddle.net/pkozlowski_opensource/XK2ZT/2/

195

Depuis que la question a été posée, l'équipe Angular a résolu ce problème en permettant la création dynamique de noms d'entrées.

Avec version angulaire 1.3 et ultérieure , vous pouvez maintenant procéder ainsi:

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

démo

Angular 1.3 a également introduit ngMessages, un outil plus puissant de validation de formulaire. Vous pouvez utiliser la même technique avec 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>
222
HoffZ

Si vous ne souhaitez pas utiliser ng-form, vous pouvez utiliser une directive personnalisée qui modifiera l'attribut name du formulaire. Placez cette directive en tant qu'attribut sur le même élément que votre modèle-ng.

Si vous utilisez d'autres directives conjointement, veillez à ne pas définir la propriété "terminal", sinon cette fonction ne pourra pas être exécutée (étant donné qu'elle a une priorité de -1).

Par exemple, lorsque vous utilisez cette directive avec ng-options, vous devez exécuter ce monkeypatch sur une ligne: 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;

      }
    };
});

Je trouve souvent utile d'utiliser ng-init pour définir l'index de $ sur un nom de variable. Par exemple:

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

Cela change votre expression régulière en:

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

Si vous avez plusieurs répétitions ng imbriquées, vous pouvez maintenant utiliser ces noms de variables à la place de $ parent. $ Index.

Définition de "terminal" et "priorité" pour les directives: https://docs.angularjs.org/api/ng/service/ $ compile # directive-definition-object

Commentaire de Github concernant la nécessité d'un monkeypatch avec l'option ng: https://github.com/angular/angular.js/commit/9ee2cdff44e7d496774b340de816344126c457b3#commitcomment-6832095https://Twitter.com/ aljohri/status/482963541520314369

MISE À JOUR:

Vous pouvez également faire ce travail avec 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
Al Johri

Utilisez la directive ng-form à l'intérieur de la balise dans laquelle vous utilisez la directive ng-repeat. Vous pouvez ensuite utiliser la portée créée par la directive ng-form pour référencer un nom générique. Par exemple:

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

Crédit pour: http://www.benlesh.com/2013/03/angular-js-validating-form-elements-in.html

11
user3761444

Ajout d'un exemple plus complexe avec "validation personnalisée" du côté du contrôleur 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
Mikita Manko

la validation fonctionne avec répétition ng si j'utilise la syntaxe suivante scope.step3Form['item[107][quantity]'].$touched Je ne sais pas si c'est une bonne pratique ou la meilleure solution, mais cela fonctionne

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

Il est trop tard, mais cela pourrait peut-être aider n'importe qui

  1. Créez un nom unique pour chaque contrôle
  2. Validez avec fromname[uniquname].$error

Exemple de code:

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

Voir travail démo ici

1
Ali Adravi

En me basant sur réponse de pkozlowski.opensource, j'ai ajouté un moyen d'avoir des noms d'entrées dynamiques qui fonctionnent également avec ngMessages . Notez la partie ng-init sur l'élément ng-form et l'utilisation de furryName. furryName devient le nom de la variable contenant la valeur de la variable input '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
ABCD.ca

En regardant ces solutions, celle fournie par Al Johri ci-dessus est la plus proche de mes besoins, mais sa directive était un peu moins programmable que je ne le voulais. Voici ma version de ses solutions:

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

Cette solution vous permet simplement de passer une expression de générateur de noms à la directive et d’éviter le verrouillage de la substitution de modèle qu’il utilisait.

J'ai également eu des problèmes au début avec cette solution car elle ne montrait pas d'exemple d'utilisation dans le balisage, voici comment je l'ai utilisée.

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

J'ai un exemple de travail plus complet sur github .

1
tomgreen98

Le nom dans la répétition ng apparaîtra séparément dans la validation du formulaire.

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

Mais j'ai eu du mal à le trouver dans son message de validation, j'ai donc dû utiliser un ng-init pour le faire résoudre une variable en tant que clé d'objet.

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

Voici un exemple de la façon dont je fais cela. Je ne sais pas si c'est la meilleure solution, mais fonctionne parfaitement.

D'abord, codez en HTML. Regardez ng-class, il appelle la fonction hasError. Regardez également la déclaration du nom de l'entrée. J'utilise l'index $ pour créer différents noms d'entrée.

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

Et maintenant, voici la fonction 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
David Martin

Si votre utilisation de ng-repeat $ index fonctionne comme ceci

  name="QTY{{$index}}"

et

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

nous devons montrer le ng-show dans 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
Kondal

Mes exigences étaient un peu différentes de celles posées dans la question initiale, mais j'espère pouvoir aider quelqu'un qui vit le même problème que moi auparavant ..

Je devais définir si un champ était requis ou non en fonction d'une variable de portée. Je devais donc essentiellement définir ng-required="myScopeVariable" (qui est une variable booléenne).

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

C'est possible et voici comment je fais la même chose avec un tableau d'entrées.

envelopper la table dans une forme comme celle-ci

Ensuite, utilisez ce

J'ai un formulaire avec des directives multi-imbriquées qui contiennent tous des entrées, sélections, etc. Ces éléments sont tous entre ng-repeats et des valeurs de chaîne dynamiques.

Voici comment utiliser la directive:

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

Remarque: vous pouvez ajouter et indexer la concaténation de chaînes si vous devez sérialiser éventuellement une table d'entrées. c'est ce que j'ai fait.

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

Cela devrait gérer de nombreuses situations où vous ne savez tout simplement pas où sera le formulaire. Ou peut-être avez-vous des formulaires imbriqués, mais pour une raison quelconque, vous souhaitez associer ce nom d'entrée à deux formulaires? Eh bien, transmettez simplement le nom du formulaire auquel vous souhaitez associer le nom de l’entrée.

Ce que je voulais, c’était un moyen d’affecter des valeurs dynamiques à des entrées que je ne saurai jamais, puis d’appeler simplement $ scope.myFormName. $ Valid.

Vous pouvez ajouter ce que vous voulez: plus de tables, plus d'entrées, formes imbriquées, comme vous voulez. Il suffit de transmettre le nom du formulaire par rapport auquel vous souhaitez valider les entrées. Ensuite, sur le formulaire, demandez si le $ scope.votreNomForm. $ Valide

0
SoEzPz