web-dev-qa-db-fra.com

Comparer deux valeurs d'entrée dans une validation de formulaire avec AngularJS

J'essaie de valider un formulaire avec AngularJS. Je suis particulièrement intéressé par la comparaison de deux valeurs. Je veux que l'utilisateur confirme certaines données qu'il a entrées avant de continuer. Disons que j'ai le code ci-dessous:

<p>
    Email:<input type="email" name="email1" ng-model="emailReg">
    Repeat Email:<input type="email" name="email2" ng-model="emailReg2">
<p>

et puis je peux utiliser la validation avec:

<span ng-show="registerForm.email1.$error.required">Required!</span>
<span ng-show="registerForm.email1.$error.email">Not valid email!</span>
<span ng-show="emailReg !== emailReg2">Emails have to match!</span>  <-- see this line

registerForm. $ valid réagira correctement quant au texte entré, sauf que je ne sais pas comment utiliser la comparaison dans cette validation pour forcer les courriels à être identiques avant de permettre à l'utilisateur de soumettre le formulaire.

J'aimerais avoir une solution sans directives personnalisées, mais si cela ne peut pas être réalisé sans elle, je vais m'en occuper. Ici est une réponse qui répond à un problème similaire avec une directive personnalisée.

Toute aide appréciée, merci

47
trainoasis

Une façon d'y parvenir est d'utiliser une directive personnalisée. Voici un exemple utilisant une directive personnalisée (ng-match dans ce cas):

<p>Email:<input type="email" name="email1" ng-model="emailReg">
Repeat Email:<input type="email" name="email2" ng-model="emailReg2" ng-match="emailReg"></p>

<span data-ng-show="myForm.emailReg2.$error.match">Emails have to match!</span>

NOTE: Il n'est généralement pas recommandé d'utiliser ng- comme préfixe pour une directive personnalisée car cela pourrait entrer en conflit avec une directive officielle AngularJS. 

Mettre à jour

Il est également possible d’obtenir cette fonctionnalité sans utiliser de directive personnalisée:

HTML

<button ng-click="add()></button>
<span ng-show="IsMatch">Emails have to match!</span>

Manette

$scope.add = function() {
  if ($scope.emailReg != $scope.emailReg2) {
    $scope.IsMatch=true;
    return false;
  }
  $scope.IsMatch=false;
}
37
Ramesh Rajendran

Vous devriez pouvoir utiliser ng-pattern/regex pour comparer 2 valeurs d'entrée

Email:<input type="email" name="email1" ng-model="emailReg">
Repeat Email:<input type="email" name="email2" ng-model="emailReg2" ng-pattern="emailReg">

et validation avec:

<span ng-show="registerForm.email2.$error.pattern">Repeat Email should have the same value with email!</span>
46
Henry Neo

trainosais - vous avez raison, la validation devrait se faire au niveau de la directive. C'est propre, modulaire et permet la réutilisation du code. Lorsque vous avez une validation de base comme celle d'un contrôleur, vous devez l'écrire encore et encore pour différents formulaires. C'est super anti-sec.

J'ai eu un problème similaire récemment et l'ai résolu avec une simple directive, qui se connecte au pipeline des analyseurs syntaxiques, reste donc cohérente avec l'architecture angulaire. Le chaînage des validateurs facilite la réutilisation, ce qui, à mon avis, devrait être considéré comme la seule solution.

Sans plus tarder, voici le balisage simplifié:

<form novalidate="novalidate">
    <label>email</label>
    <input type="text"
        ng-model="email"
        name="email" />
    <label>email repeated</label>
    <input ng-model="emailRepeated"
        same-as="email"
        name="emailRepeated" />
</form>

Et le code JS:

angular.module('app', [])
    .directive('sameAs', function() {
        return {
            require: 'ngModel',
            link: function(scope, elem, attrs, ngModel) {
                ngModel.$parsers.unshift(validate);

                // Force-trigger the parsing pipeline.
                scope.$watch(attrs.sameAs, function() {
                    ngModel.$setViewValue(ngModel.$viewValue);
                });

                function validate(value) {
                    var isValid = scope.$eval(attrs.sameAs) == value;

                    ngModel.$setValidity('same-as', isValid);

                    return isValid ? value : undefined;
                }
            }
        };
    });

La directive se connecte au pipeline d'analyseurs afin d'être notifiée de toute modification de la valeur d'affichage et de définir sa validité en fonction de la comparaison entre la nouvelle valeur d'affichage et la valeur du champ de référence. Ce bit est facile. Le problème le plus difficile consiste à détecter les modifications apportées au champ de référence. Pour cela, la directive définit un observateur sur la valeur de référence et déclenche de force le pipeline d'analyse, afin que tous les validateurs soient exécutés à nouveau.

Si vous voulez jouer avec, voici mon stylo: http://codepen.io/jciolek/pen/kaKEn

J'espère que ça aide, Jacek

29
Jacek Ciolek

J'ai récemment écrit une directive personnalisée qui peut être assez générique pour effectuer n'importe quelle validation. Il faut une fonction de validation de la portée actuelle 

module.directive('customValidator', [function () {
        return {
            restrict: 'A',
            require: 'ngModel',
            scope: { validateFunction: '&' },
            link: function (scope, Elm, attr, ngModelCtrl) {
                ngModelCtrl.$parsers.Push(function (value) {
                    var result = scope.validateFunction({ 'value': value });
                    if (result || result === false) {
                        if (result.then) {
                            result.then(function (data) {           //For promise type result object
                                ngModelCtrl.$setValidity(attr.customValidator, data);
                            }, function (error) {
                                ngModelCtrl.$setValidity(attr.customValidator, false);
                            });
                        }
                        else {
                            ngModelCtrl.$setValidity(attr.customValidator, result);
                            return result ? value : undefined;      //For boolean result return based on boolean value
                        }
                    }
                    return value;
                });
            }
        };
    }]);

Pour l'utiliser vous faites

<input type="email" name="email2" ng-model="emailReg2" custom-validator='emailMatch' data-validate-function='checkEmailMatch(value)'>
<span ng-show="registerForm.email2.$error.emailMatch">Emails have to match!</span>

Dans votre contrôleur, vous pouvez alors implémenter la méthode qui doit renvoyer true ou false

$scope.checkEmailMatch=function(value) {
    return value===$scope.emailReg;
}

L'avantage est qu'il n'est pas nécessaire d'écrire une directive personnalisée pour chaque validation personnalisée.

12
Chandermani

Lors de la mise à niveau d'angle angulaire à 1.3 et au-dessus, j'ai trouvé un problème en utilisant La réponse géniale de Jacek Ciolek :

  • Ajouter des données au champ de référence
  • Ajouter les mêmes données au champ avec la directive dessus (ce champ est maintenant valide)
  • Revenir au champ de référence et modifier les données (le champ de la directive reste valide) 

J'ai testé la réponse de rdukeshier (mise à jour de var modelToMatch = element.attr('sameAs') en var modelToMatch = attrs.sameAs pour extraire correctement le modèle de référence), mais le même problème s'est produit.

Pour résoudre ce problème (testé dans les angles 1.3 et 1.4), j'ai adapté le code de rdukeshier et ajouté un observateur sur le champ de référence pour exécuter toutes les validations lorsque le champ de référence est modifié. La directive ressemble maintenant à ceci:

angular.module('app', [])
  .directive('sameAs', function () {
    return {
      require: 'ngModel',
      link: function(scope, element, attrs, ctrl) {
        var modelToMatch = attrs.sameAs;      

        scope.$watch(attrs.sameAs, function() {
          ctrl.$validate();          
        })

        ctrl.$validators.match = function(modelValue, viewValue) {
          return viewValue === scope.$eval(modelToMatch);
        };
      }
   };
});

Codepen mis à jour

7
br3w5

use ng-pattern, pour que ng-valid et ng-dirty puissent agir correctement

Email:<input type="email" name="email1" ng-model="emailReg">
Repeat Email:<input type="email" name="email2" ng-model="emailReg2" ng-pattern="emailReg">

<span ng-show="registerForm.email2.$error.pattern">Emails have to match!</span>
5
Cheng Li

Pas besoin d'une fonction ou d'une directive. Il suffit de comparer leur valeur $ modelValue à partir de la vue: 

ng-show="formName.email.$modelValue !== formName.confirmEmail.$modelValue"

Exemple plus détaillé: 

<span ng-show="(formName.email.$modelValue !== formName.confirmEmail.$modelValue) 
                && formName.confirmEmail.$touched
                && !formName.confirmEmail.$error.required">Email does not match.</span>

Veuillez noter que ConfirmEmail est en dehors du ViewModel; c'est la propriété de la portée $. Il n'a pas besoin d'être soumis.

4

Voici ma version simple de la directive de validation personnalisée:

angular.module('app')
  .directive('equalsTo', function () {
    return {
      require: 'ngModel',
      link:    function (scope, Elm, attrs, ngModel) {
        scope.$watchGroup([attrs.equalsTo, () => ngModel.$modelValue], newVal => {
          ngModel.$setValidity('equalsTo', newVal[0] === newVal[1]);
        });
      }
    };
  })
1
Yura Fedoriv

Ce module fonctionne bien pour comparer deux champs. Fonctionne très bien avec Angular 1.3+. Simple à utiliser https://www.npmjs.com/package/angular-password

Cela permet également de sauvegarder le module en tant que générique. Il suffit de les inclure dans la liste des paquets de votre module.

1
ravi punjwani

La méthode de @ Henry-Neo était proche, il fallait juste des règles Regex plus strictes.

<form name="emailForm">
    Email: <input type="email" name="email1" ng-model="emailReg">
    Repeat Email: <input type="email" name="email2" ng-model="emailReg2" ng-pattern="(emailReg)">
</form>

En incluant la règle regex entre parenthèses, elle correspondra à la chaîne entière de emailReg à emailReg2 et entraînera l'échec de la validation du formulaire car elle ne correspond pas.

Vous pouvez ensuite explorer les éléments pour déterminer quelle est la pièce défaillante.

 <p ng-show="emailForm.$valid">Form Valid</p>
 <p ng-show="emailForm.email1.$error">Email not valid</p>
 <p ng-show="emailForm.email1.$valid && emailForm.email1.$error.pattern">
     Emails Do Not Match
 </p>
1
dmo

Voici une version 1.3 angulaire de la même directive:

angular.module('app').directive('sameAs', [function() {
  'use strict';

  return {
    require: 'ngModel',
    restrict: 'A',
    link: function(scope, element, attrs, ctrl) {
      var modelToMatch = element.attr('sameAs');      
      ctrl.$validators.match = function(modelValue, viewValue) {
        return viewValue === scope.$eval(modelToMatch);
      };
    }
  };
}]);
0
rdukeshier

Bien sûr, pour des comparaisons très simples, vous pouvez toujours utiliser ngMin/ngMax.

Sinon, vous pouvez utiliser une directive personnalisée et il y a inutile de faire un $watch, un $observe ou un $eval ou ce fantaisie $setValidity dans les deux sens. De plus, il n’ya pas besoin de vous connecter à la fonction postLink. Essayez de rester en dehors du DOM autant que possible, car cela va à l’encontre de l’esprit angulaire.

Il suffit d’utiliser les cycles de cycle de vie que le framework vous donne. Ajoutez un validateur et $validate à chaque modification. Aussi simple que cela.

app.directive('sameAs', function() {
  return {
    restrict: 'A',
    require: {
      ngModelCtrl: 'ngModel'
    },
    scope: {
      reference: '<sameAs'
    },
    bindToController: true,
    controller: function($scope) {
      var $ctrl = $scope.$ctrl;

      //add the validator to the ngModelController
      $ctrl.$onInit = function() {
        function sameAsReference (modelValue, viewValue) {
          if (!$ctrl.reference || !modelValue) { //nothing to compare
            return true;
          }
          return modelValue === $ctrl.reference;
        }
        $ctrl.ngModelCtrl.$validators.sameas = sameAsReference;
      };

      //do the check at each change
      $ctrl.$onChanges = function(changesObj) {
        $ctrl.ngModelCtrl.$validate();
      };
    },
    controllerAs: '$ctrl'
  };
});

Votre plunker .

0
Daniele Repici

Merci pour le bon exemple @Jacek Ciolek . Pour la version 1.3.x angulaire, cette solution est interrompue lorsque des mises à jour sont apportées à la valeur d'entrée de référence. S'appuyant sur cet exemple pour 1.3.x angulaire, cette solution fonctionne tout aussi bien avec Angular 1.3.x. Il lie et surveille les modifications de la valeur de référence.

angular.module('app', []).directive('sameAs', function() {
  return {
    restrict: 'A',
    require: 'ngModel',
    scope: {
      sameAs: '='
    },
    link: function(scope, Elm, attr, ngModel) {
      if (!ngModel) return;

      attr.$observe('ngModel', function(value) {
        // observes changes to this ngModel
        ngModel.$validate();
      });

      scope.$watch('sameAs', function(sameAs) {
        // watches for changes from sameAs binding
        ngModel.$validate();
      });

      ngModel.$validators.sameAs = function(value) {
        return scope.sameAs == value;
      };
    }
  };
});

Voici mon stylo: http://codepen.io/kvangrae/pen/BjxMWR

0
Kurt Vangraefschepe

Je dois le faire sous une seule forme dans l’ensemble de mon application et je vois une directive comme une super complication dans mon cas. J’utilise donc le ng-patter comme un point, mais j’ai quelques problèmes lorsque la chaîne contient des caractères spéciaux comme .[\ this cassé, donc je crée une fonction pour les caractères spéciaux scape.

$scope.escapeRegExp(str) {
  return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}

et dans la vue

<form name="ExampleForm">
  <label>Password</label>
  <input ng-model="password" required />
  <br>
   <label>Confirm password</label>
  <input ng-model="confirmPassword" required ng-pattern="escapeRegExp(password)"/>  
</form>
0
stalin

Vous devez regarder le plus gros problème. Comment écrire les directives qui résolvent un problème. Vous devriez essayer la directive use-form-error . Cela aiderait-il à résoudre ce problème et bien d’autres.

    <form name="ExampleForm">
  <label>Password</label>
  <input ng-model="password" required />
  <br>
   <label>Confirm password</label>
  <input ng-model="confirmPassword" required />
  <div use-form-error="isSame" use-error-expression="password && confirmPassword && password!=confirmPassword" ng-show="ExampleForm.$error.isSame">Passwords Do Not Match!</div>
</form>

Exemple live jsfiddle

0
Stepan Kasyanenko

Le mien est similaire à votre solution mais je l’ai fait fonctionner. La seule différence est mon modèle. J'ai les modèles suivants dans mon entrée html:

ng-model="new.Participant.email"
ng-model="new.Participant.confirmEmail"

et dans mon contrôleur, j'ai ceci dans $ scope:

 $scope.new = {
        Participant: {}
    };

et cette ligne de validation a fonctionné:

<label class="help-block" ng-show="new.Participant.email !== new.Participant.confirmEmail">Emails must match! </label>
0
r.sangria

J'ai modifié la méthode de Chandermani pour être compatible avec Angularjs 1.3 et supérieur. Migré de $ parsers vers $ asyncValidators.

module.directive('customValidator', [function () {
    return {
        restrict: 'A',
        require: 'ngModel',
        scope: { validateFunction: '&' },
        link: function (scope, Elm, attr, ngModelCtrl) {
            ngModelCtrl.$asyncValidators[attr.customValidator] = function (modelValue, viewValue) {
                return new Promise(function (resolve, reject) {
                    var result = scope.validateFunction({ 'value': viewValue });
                    if (result || result === false) {
                        if (result.then) {
                            result.then(function (data) {           //For promise type result object
                                if (data)
                                    resolve();
                                else
                                    reject();
                            }, function (error) {
                                reject();
                            });
                        }
                        else {
                            if (result)
                                resolve();
                            else
                                reject();
                            return;
                        }
                    }
                    reject();
                });
            }

        }
    };
}]);

L'utilisation est la même

0
Proggear