171 votes

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

J'ai un tableau qui est créé en utilisant ng-repeat. Je veux ajouter la validation à chaque élément du tableau. Le problème est que chaque cellule de saisie porte le même nom que la cellule située au-dessus et au-dessous d'elle. J'ai essayé d'utiliser la fonction {{$index}} pour nommer les entrées, mais bien que les littéraux de chaîne en HTML semblent corrects, cela fonctionne maintenant.

Voici mon code à ce jour :

<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 retirer le {{}} à partir de l'index, mais cela ne fonctionne pas non plus. Pour l'instant, la propriété de validation de l'entrée fonctionne correctement, mais le message d'erreur ne s'affiche pas.

Quelqu'un a-t-il des suggestions ?

Edit : En plus des excellentes réponses ci-dessous, voici un article de blog qui traite de cette question de manière plus détaillée : http://www.thebhwgroup.com/blog/2014/08/angularjs-html-form-design-part-2/

5 votes

Pour ceux qui lisent ceci en 2015... la réponse la plus votée n'est plus la bonne. Regardez plus bas. :)

0 votes

Ce site semble être la réponse "pour 2015" dont parle @WillStrohl.

0 votes

Quelle est l'étiquette appropriée pour les SO ici ? Devrais-je laisser la réponse acceptée puisqu'elle était correcte à l'époque ou accepter la réponse correcte d'aujourd'hui ? Je veux juste que ce fil apparemment populaire soit utile aux nouveaux visiteurs.

231voto

HoffZ Points 1008

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

Avec Angular version 1.3 et ultérieure vous pouvez maintenant le faire :

<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 pour la validation des formulaires. 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>

197voto

pkozlowski.opensource Points 52557

AngularJS s'appuie sur les noms des entrées pour exposer les erreurs de validation.

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

Pour résoudre le problème du "nom dynamique", il faut 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 solution serait d'écrire une directive personnalisée pour cela.

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

0 votes

Merci pour votre réponse. J'ai toujours un problème après avoir utilisé des formulaires et je me demandais si vous saviez de mémoire si l'utilisation de tableaux ou de la validation ng-patter pouvait entraîner la rupture de cette solution.

0 votes

AngularJS utilise la traversée du DOM pour compiler ses modèles. Votre HTML doit donc être valide. Si vous pouviez préparer un jsFiddle avec votre modèle, il serait plus facile de vous aider.

0 votes

J'ai trouvé le problème. C'était simple après avoir regardé vos liens. Merci beaucoup d'avoir fourni rapidement une solution que je vais utiliser souvent !

13voto

Al Johri Points 196

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 comme un attribut sur le même élément que votre ng-model.

Si vous utilisez d'autres directives en conjonction, veillez à ce qu'elles n'aient pas la propriété "terminal", sinon cette fonction ne pourra pas s'exécuter (é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 d'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 le $index à 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 ng-repeats imbriqués, vous pouvez désormais utiliser ces noms de variables au lieu de $parent.$index.

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

Commentaire de Github concernant le besoin de ng-option monkeypatch : https://github.com/angular/angular.js/commit/9ee2cdff44e7d496774b340de816344126c457b3#commitcomment-6832095 https://twitter.com/aljohri/status/482963541520314369

UPDATE :

Vous pouvez également faire en sorte que cela fonctionne 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;
            }
          }
        }
      }
    };
});

3 votes

Pour que les choses soient claires, le fait que cette réponse ne soit pas sélectionnée ne signifie pas qu'elle n'est pas la meilleure. Elle vient d'être publiée près de deux ans après que la question a été posée à l'origine. Je prendrais en considération cette réponse et celle de TomGreen en plus de la réponse sélectionnée si vous rencontrez le même problème.

11voto

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 à : http://www.benlesh.com/2013/03/angular-js-validating-form-elements-in.html

3voto

Mikita Manko Points 71

Ajout d'un exemple plus complexe avec une "validation personnalisée" sur le 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>

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X