99 votes

Validation dynamique et nom dans un formulaire avec AngularJS

J'ai ce formulaire : http://jsfiddle.net/dfJeN/

Comme vous pouvez le voir, la valeur du nom de l'entrée est définie de manière statique :

name="username"

La validation du formulaire fonctionne bien (ajouter quelque chose et supprimer tout le texte de l'entrée, un texte doit apparaître).

J'essaie ensuite de définir dynamiquement la valeur du nom : http://jsfiddle.net/jNWB8/

name="{input.name}"

Ensuite, j'applique ceci à ma validation

login.{{input.name}}.$error.required

(ce modèle sera utilisé dans un ng-repeat) mais la validation de mon formulaire ne fonctionne pas. Elle est correctement interprétée dans mon navigateur (si j'inspecte l'élément, je vois login.username.$error.required).

Une idée ?

EDIT : Après avoir enregistré le champ d'application dans la console, il s'avère que le fichier

{{input.name}}

L'expression n'est pas interpolée. Mon formulaire a un attribut {{input.name}} mais pas de nom d'utilisateur.

MISE À JOUR : depuis la version 1.3.0-rc.3, name="{{input.name}}" fonctionne comme prévu. Veuillez consulter #1404

0 votes

Après quelques recherches, j'ai trouvé ceci : "Un scénario dans lequel l'utilisation de ngBind est préférée à la liaison {{ expression }} est lorsqu'il est souhaitable de mettre des liaisons dans le modèle qui est momentanément affiché par le navigateur dans son état brut avant qu'Angular ne le compile". Dans cette page docs.angularjs.org/api/ng.directive:ngBind mais cela semble être un bon début pour ce que j'essaie de faire. Ce post sera mis à jour si je trouve une solution.

0 votes

Il y a un problème ouvert sur github github.com/angular/angular.js/issues/1404

0 votes

L'une de ces réponses a-t-elle résolu votre problème ? Si c'est le cas, veuillez l'indiquer comme étant la réponse en cliquant sur la marque de contrôle sous son score.

177voto

Ben Lesh Points 39290

Tu ne peux pas faire ce que tu essaies de faire de cette façon.

En supposant que ce que vous essayez de faire est d'ajouter dynamiquement des éléments à un formulaire, avec quelque chose comme un ng-repeat, vous devez utiliser des éléments imbriqués. ng-form pour permettre la validation de ces éléments individuels :

<form name="outerForm">
<div ng-repeat="item in items">
   <ng-form name="innerForm">
      <input type="text" name="foo" ng-model="item.foo" />
      <span ng-show="innerForm.foo.$error.required">required</span>
   </ng-form>
</div>
<input type="submit" ng-disabled="outerForm.$invalid" />
</form>

Malheureusement, ce n'est pas une fonctionnalité bien documentée d'Angular.

12 votes

Comment avez-vous fini par résoudre ce problème ? Je ne vois toujours pas en quoi cette réponse particulière est liée à votre problème - puisqu'elle ne montre pas les champs de formulaire et les noms générés dynamiquement ?

7 votes

Il s'agit d'une solution complète (ou d'une solution de contournement) et de l'approche suggérée par l'équipe angulaire (à partir de docs.angularjs.org/api/ng.directive:form ) : "Puisque vous ne pouvez pas générer dynamiquement l'attribut de nom des éléments d'entrée en utilisant l'interpolation, vous devez envelopper chaque ensemble d'entrées répétées dans une directive ngForm et les imbriquer dans un élément de formulaire externe." Chaque formulaire imbriqué a sa propre portée, ce qui permet à cette méthode de fonctionner.

2 votes

Cet exemple et cette suggestion ne traitent toujours pas du "nom" dynamique. On dirait qu'ils veulent vous permettre d'imbriquer dynamiquement des ensembles de champs "clonés", mais que le nom sous-jacent de chaque champ doit être statique.

44voto

EnISeeK Points 348

L'utilisation de ngForm imbriqués vous permet d'accéder au InputController spécifique à partir du modèle HTML. Toutefois, si vous souhaitez y accéder à partir d'un autre contrôleur, cela ne sert à rien.

par exemple

<script>
  function OuterController($scope) {
    $scope.inputName = 'dynamicName';

    $scope.doStuff = function() {
      console.log($scope.formName.dynamicName); // undefined
      console.log($scope.formName.staticName); // InputController
    }
  }
</script>

<div controller='OuterController'>
  <form name='myForm'>
    <input name='{{ inputName }}' />
    <input name='staticName' />
  </form>
  <a ng-click='doStuff()'>Click</a>
</div>

J'utilise cette directive pour aider à résoudre le problème :

angular.module('test').directive('dynamicName', function($compile, $parse) {
  return {
    restrict: 'A',
    terminal: true,
    priority: 100000,
    link: function(scope, elem) {
      var name = $parse(elem.attr('dynamic-name'))(scope);
      // $interpolate() will support things like 'skill'+skill.id where parse will not
      elem.removeAttr('dynamic-name');
      elem.attr('name', name);
      $compile(elem)(scope);
    }
  };
});

Vous pouvez désormais utiliser des noms dynamiques partout où cela est nécessaire, en utilisant simplement l'attribut "dynamic-name" au lieu de l'attribut "name".

par exemple

<script>
  function OuterController($scope) {
    $scope.inputName = 'dynamicName';

    $scope.doStuff = function() {
      console.log($scope.formName.dynamicName); // InputController
      console.log($scope.formName.staticName); // InputController
    }
  }
</script>

<div controller='OuterController'>
  <form name='myForm'>
    <input dynamic-name='inputName' />
    <input name='staticName' />
  </form>
  <a ng-click='doStuff()'>Click</a>
</div>

1 votes

J'ai utilisé cette solution à l'exception de l'utilisation de $interpolate au lieu de $parse a été plus utile.

0 votes

Je vois que tu fais du termial:true. Qu'est-ce que cela signifie ? Puis-je utiliser cette directive sur les formulaires également, par exemple <form ng-repeat="item in items" dynamic-name="'item'+item.id"> ... <span ng-show="item{{item.id}}.$invalid">This form is invalid</span></form> ?

16voto

Paolo Moretti Points 9519

Le problème devrait être corrigé dans AngularJS 1.3, selon cette discussion sur Github .

En attendant, voici une solution temporaire créée par @caitp et @Thinkscape :

// Workaround for bug #1404
// https://github.com/angular/angular.js/issues/1404
// Source: http://plnkr.co/edit/hSMzWC?p=preview
app.config(['$provide', function($provide) {
    $provide.decorator('ngModelDirective', function($delegate) {
        var ngModel = $delegate[0], controller = ngModel.controller;
        ngModel.controller = ['$scope', '$element', '$attrs', '$injector', function(scope, element, attrs, $injector) {
            var $interpolate = $injector.get('$interpolate');
            attrs.$set('name', $interpolate(attrs.name || '')(scope));
            $injector.invoke(controller, this, {
                '$scope': scope,
                '$element': element,
                '$attrs': attrs
            });
        }];
        return $delegate;
    });
    $provide.decorator('formDirective', function($delegate) {
        var form = $delegate[0], controller = form.controller;
        form.controller = ['$scope', '$element', '$attrs', '$injector', function(scope, element, attrs, $injector) {
            var $interpolate = $injector.get('$interpolate');
            attrs.$set('name', $interpolate(attrs.name || attrs.ngForm || '')(scope));
            $injector.invoke(controller, this, {
                '$scope': scope,
                '$element': element,
                '$attrs': attrs
            });
        }];
        return $delegate;
    });
}]);

Démonstration sur JSFiddle .

1 votes

Pour ceux qui sont bloqués sur la version 1.2, c'est la solution la plus simple à mettre en œuvre.

14voto

srfrnk Points 148

Une bonne idée de @EnISeeK.... mais j'ai réussi à la rendre plus élégante et moins gênante pour les autres directives :

.directive("dynamicName",[function(){
    return {
        restrict:"A",
        require: ['ngModel', '^form'],
        link:function(scope,element,attrs,ctrls){
            ctrls[0].$name = scope.$eval(attrs.dynamicName) || attrs.dynamicName;
            ctrls[1].$addControl(ctrls[0]);
        }
    };
}])

1 votes

J'ajouterais seulement ce qui suit : ctrls[0].$name = scope.$eval(attrs.dynamicName) || attrs.dynamicName ;

7voto

jason zhang Points 96

Juste une petite amélioration par rapport à la solution EnlSeek

angular.module('test').directive('dynamicName', ["$parse", function($parse) {
 return {
    restrict: 'A',
    priority: 10000, 
    controller : ["$scope", "$element", "$attrs", 
           function($scope, $element, $attrs){
         var name = $parse($attrs.dynamicName)($scope);
         delete($attrs['dynamicName']);
         $element.removeAttr('data-dynamic-name');
         $element.removeAttr('dynamic-name');
          $attrs.$set("name", name);
    }]

  };
}]);

Voici un essai de plunker . Voici une explication détaillée

0 votes

+1, la directive d'EnlSeek provoquait une boucle infinie dans ma directive ; j'ai dû supprimer les éléments 'fx' de cette réponse pour qu'elle fonctionne.

0 votes

La priorité peut interférer avec un ensemble de champs qui porteraient le même nom mais qui ont des ng-if. par ex : <input type='text' dynamic-name='foo' ng-if='field.type == "text" /> <textarea dynamic-name='foo' ng-if='field.type == "textarea"></textarea> La suppression de la 'priorité : 10000' a résolu le problème pour moi et semble toujours fonctionner correctement.

0 votes

NgIf a la priorité 600. Attribuer une priorité inférieure à 600 à cette directive devrait la faire fonctionner avec la ngIf.

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