web-dev-qa-db-fra.com

Comment ajouter une validation personnalisée à un formulaire AngularJS?

J'ai un formulaire avec des champs de saisie et une configuration de validation en ajoutant les attributs required et autres. Mais pour certains champs, je dois faire une validation supplémentaire. Comment puis-je "accéder" à la validation que FormController contrôle? 

La validation personnalisée pourrait être quelque chose comme "si ces 3 champs sont remplis, alors ce champ est obligatoire et doit être formaté de manière particulière".

Il y a une méthode dans FormController.$setValidity mais cela ne ressemble pas à une API publique, je préfère donc ne pas l'utiliser. La création d'une directive personnalisée et l'utilisation de NgModelController ressemblent à une autre option, mais nécessiteraient essentiellement que je crée une directive pour chaque règle de validation personnalisée, ce que je ne souhaite pas.

En fait, marquer un champ du contrôleur comme invalide (tout en maintenant FormController en synchronisation) pourrait être ce dont j'ai besoin dans le scénario le plus simple pour faire le travail, mais je ne sais pas comment le faire.

271
botteaap

Edit: ajout d'informations ci-dessous sur ngMessages (> = 1.3.X).

Messages de validation de formulaire standard (1.0.X et plus)

Étant donné que c’est l’un des meilleurs résultats si vous Google "Validation de la forme angulaire", je souhaite ajouter une autre réponse à cette question à toute personne venant de là.

Il existe une méthode dans FormController. $ SetValidity mais cela ne ressemble pas à une API publique, je préfère donc ne pas l'utiliser. 

C'est "public", pas de souci. Utilise le. C'est pour ça. S'il n'avait pas été conçu pour être utilisé, les développeurs Angular l'auraient privatisé dans une fermeture.

Pour effectuer une validation personnalisée, si vous ne souhaitez pas utiliser Angular-UI comme autre réponse suggérée, vous pouvez simplement lancer votre propre directive de validation.

app.directive('blacklist', function (){ 
   return {
      require: 'ngModel',
      link: function(scope, elem, attr, ngModel) {
          var blacklist = attr.blacklist.split(',');

          //For DOM -> model validation
          ngModel.$parsers.unshift(function(value) {
             var valid = blacklist.indexOf(value) === -1;
             ngModel.$setValidity('blacklist', valid);
             return valid ? value : undefined;
          });

          //For model -> DOM validation
          ngModel.$formatters.unshift(function(value) {
             ngModel.$setValidity('blacklist', blacklist.indexOf(value) === -1);
             return value;
          });
      }
   };
});

Et voici quelques exemples d'utilisation:

<form name="myForm" ng-submit="doSomething()">
   <input type="text" name="fruitName" ng-model="data.fruitName" blacklist="coconuts,bananas,pears" required/>
   <span ng-show="myForm.fruitName.$error.blacklist">
      The phrase "{{data.fruitName}}" is blacklisted</span>
   <span ng-show="myForm.fruitName.$error.required">required</span>
   <button type="submit" ng-disabled="myForm.$invalid">Submit</button>
</form>

Note: dans 1.2.X, il est probablement préférable de remplacer ng-if par ng-show ci-dessus

Voici un lien obligatoire plunker link

De plus, j'ai écrit quelques articles de blog sur ce sujet qui vont un peu plus en détail:

Validation de forme angulaire

Directives de validation personnalisées

Edit: utilisation de ngMessages dans 1.3.X

Vous pouvez maintenant utiliser le module ngMessages au lieu de ngShow pour afficher vos messages d'erreur. Cela fonctionnera avec n'importe quoi, cela ne doit pas forcément être un message d'erreur, mais voici l'essentiel:

  1. Inclure <script src="angular-messages.js"></script>
  2. Référence ngMessages dans votre déclaration de module:

    var app = angular.module('myApp', ['ngMessages']);
    
  3. Ajoutez le balisage approprié:

    <form name="personForm">
      <input type="email" name="email" ng-model="person.email" required/>
    
      <div ng-messages="personForm.email.$error">
        <div ng-message="required">required</div>
        <div ng-message="email">invalid email</div>
      </div>
    </form>
    

Dans le balisage ci-dessus, ng-message="personForm.email.$error" spécifie fondamentalement un contexte pour les directives enfants ng-message. Ensuite, ng-message="required" et ng-message="email" spécifient les propriétés à surveiller dans ce contexte. Plus important encore, ils spécifient également un ordre pour les vérifier dans. Le premier qu'il trouve dans la liste et qui est "vérité" gagne, et il montrera ce message et aucun des autres.

Et un plunker pour l'exemple ngMessages

361
Ben Lesh

Le projet Angular-UI inclut une directive ui-validate, qui vous aidera probablement dans cette tâche. Il vous permet de spécifier une fonction à appeler pour effectuer la validation.

Consultez la page de démonstration: http://angular-ui.github.com/ , recherchez l’en-tête Valider.

De la page de démonstration:

<input ng-model="email" ui-validate='{blacklist : notBlackListed}'>
<span ng-show='form.email.$error.blacklist'>This e-mail is black-listed!</span>

puis dans votre contrôleur:

function ValidateCtrl($scope) {
  $scope.blackList = ['[email protected]','[email protected]'];
  $scope.notBlackListed = function(value) {
    return $scope.blackList.indexOf(value) === -1;
  };
}
91
Pete BD

Vous pouvez utiliser ng-required pour votre scénario de validation ("si ces 3 champs sont remplis, alors ce champ est obligatoire":

<div ng-app>
    <input type="text" ng-model="field1" placeholder="Field1">
    <input type="text" ng-model="field2" placeholder="Field2">
    <input type="text" ng-model="field3" placeholder="Field3">
    <input type="text" ng-model="dependentField" placeholder="Custom validation"
        ng-required="field1 && field2 && field3">
</div>
44
Mario G.

Vous pouvez utiliser Angular-Validator .

Exemple: utiliser une fonction pour valider un champ

<input  type = "text"
    name = "firstName"
    ng-model = "person.firstName"
    validator = "myCustomValidationFunction(form.firstName)">

Ensuite, dans votre contrôleur, vous aurez quelque chose comme

$scope.myCustomValidationFunction = function(firstName){ 
   if ( firstName === "John") {
       return true;
    }

Vous pouvez aussi faire quelque chose comme ça:

<input  type = "text"
        name = "firstName"
        ng-model = "person.firstName"
        validator = "'!(field1 && field2 && field3)'"
        invalid-message = "'This field is required'">

(où champ1 champ2 et champ3 sont des variables de portée. Vous pouvez également vérifier si les champs ne correspondent pas à la chaîne vide)

Si le champ ne passe pas le validator, le champ sera marqué comme non valide et l'utilisateur ne pourra pas soumettre le formulaire.

Pour plus de cas d'utilisation et d'exemples, voir: https://github.com/turinggroup/angular-validator

_ {Disclaimer: Je suis l'auteur de Angular-Validator} _

28
user3920706

Voici une méthode intéressante pour effectuer des validations d’expression générique personnalisées dans un formulaire (à partir de: Validation de formulaire avancée avec AngularJS et les filtres ):

<form novalidate="">  
   <input type="text" id="name" name="name" ng-model="newPerson.name"
      ensure-expression="(persons | filter:{name: newPerson.name}:true).length !== 1">
   <!-- or in your case:-->
   <input type="text" id="fruitName" name="fruitName" ng-model="data.fruitName"
      ensure-expression="(blacklist | filter:{fruitName: data.fruitName}:true).length !== 1">
</form>
app.directive('ensureExpression', ['$http', '$parse', function($http, $parse) {
    return {
        require: 'ngModel',
        link: function(scope, ele, attrs, ngModelController) {
            scope.$watch(attrs.ngModel, function(value) {
                var booleanResult = $parse(attrs.ensureExpression)(scope);
                ngModelController.$setValidity('expression', booleanResult);
            });
        }
    };
}]);

jsFiddle demo (supporte la dénomination des expressions et les expressions multiples)

C'est similaire à ui-validate, mais vous n'avez pas besoin d'une fonction de validation spécifique à la portée (cela fonctionne de manière générique) et bien sûr vous n'avez pas besoin de ui.utils de cette façon.

12
Benny Bottema

J'ai récemment créé une directive permettant l'invalidation des entrées de forme angulaire basée sur l'expression. Toute expression angulaire valide peut être utilisée et prend en charge les clés de validation personnalisées utilisant la notation objet. Testé avec angulaire v1.3.8

        .directive('invalidIf', [function () {
        return {
            require: 'ngModel',
            link: function (scope, Elm, attrs, ctrl) {

                var argsObject = scope.$eval(attrs.invalidIf);

                if (!angular.isObject(argsObject)) {
                    argsObject = { invalidIf: attrs.invalidIf };
                }

                for (var validationKey in argsObject) {
                    scope.$watch(argsObject[validationKey], function (newVal) {
                        ctrl.$setValidity(validationKey, !newVal);
                    });
                }
            }
        };
    }]);

Vous pouvez l'utiliser comme ceci:

<input ng-model="foo" invalid-if="{fooIsGreaterThanBar: 'foo > bar',
                                   fooEqualsSomeFuncResult: 'foo == someFuncResult()'}/>

Ou simplement en passant une expression (la clé de validation par défaut de "invalidIf" lui sera attribuée)

<input ng-model="foo" invalid-if="foo > bar"/>
11
Alex Schwartz

@synergetic Je pense que @blesh suppose de valider la fonction ci-dessous

function validate(value) {
    var valid = blacklist.indexOf(value) === -1;
    ngModel.$setValidity('blacklist', valid);
    return valid ? value : undefined;
}

ngModel.$formatters.unshift(validate);
ngModel.$parsers.unshift(validate);
4
Atul Chaudhary

Mettre à jour:

Version améliorée et simplifiée de la directive précédente (une au lieu de deux) avec les mêmes fonctionnalités:

.directive('myTestExpression', ['$parse', function ($parse) {
    return {
        restrict: 'A',
        require: 'ngModel',
        link: function (scope, element, attrs, ctrl) {
            var expr = attrs.myTestExpression;
            var watches = attrs.myTestExpressionWatch;

            ctrl.$validators.mytestexpression = function (modelValue, viewValue) {
                return expr == undefined || (angular.isString(expr) && expr.length < 1) || $parse(expr)(scope, { $model: modelValue, $view: viewValue }) === true;
            };

            if (angular.isString(watches)) {
                angular.forEach(watches.split(",").filter(function (n) { return !!n; }), function (n) {
                    scope.$watch(n, function () {
                        ctrl.$validate();
                    });
                });
            }
        }
    };
}])

Exemple d'utilisation:

<input ng-model="price1" 
       my-test-expression="$model > 0" 
       my-test-expression-watch="price2,someOtherWatchedPrice" />
<input ng-model="price2" 
       my-test-expression="$model > 10" 
       my-test-expression-watch="price1" 
       required />

Résultat: expressions de test mutuellement dépendantes dans lesquelles les validateurs sont exécutés lors du changement de modèle de directive et de modèle actuel. 

L'expression test a une variable locale $model que vous devez utiliser pour la comparer à d'autres variables.

Auparavant:

J'ai tenté d'améliorer le code @Plantface en ajoutant une directive supplémentaire. Cette directive supplémentaire est très utile si notre expression doit être exécutée lorsque des modifications sont apportées à plusieurs variables ngModel.

.directive('ensureExpression', ['$parse', function($parse) {
    return {
        restrict: 'A',
        require: 'ngModel',
        controller: function () { },
        scope: true,
        link: function (scope, element, attrs, ngModelCtrl) {
            scope.validate = function () {
                var booleanResult = $parse(attrs.ensureExpression)(scope);
                ngModelCtrl.$setValidity('expression', booleanResult);
            };

            scope.$watch(attrs.ngModel, function(value) {
                scope.validate();
            });
        }
    };
}])

.directive('ensureWatch', ['$parse', function ($parse) {
    return {
        restrict: 'A',
        require: 'ensureExpression',
        link: function (scope, element, attrs, ctrl) {
            angular.forEach(attrs.ensureWatch.split(",").filter(function (n) { return !!n; }), function (n) {
                scope.$watch(n, function () {
                    scope.validate();
                });
            });
        }
    };
}])

Exemple d'utilisation de celui-ci pour créer des champs croisés validés:

<input name="price1"
       ng-model="price1" 
       ensure-expression="price1 > price2" 
       ensure-watch="price2" />
<input name="price2" 
       ng-model="price2" 
       ensure-expression="price2 > price3" 
       ensure-watch="price3" />
<input name="price3" 
       ng-model="price3" 
       ensure-expression="price3 > price1 && price3 > price2" 
       ensure-watch="price1,price2" />

ensure-expression est exécuté pour valider le modèle lorsque ng-model ou l'une des variables ensure-watch est modifiée.

4
knr

Dans AngularJS, le meilleur endroit pour définir la validation personnalisée est la directive Cutsom . AngularJS fournit un module ngMessages.

ngMessages est une directive conçue pour afficher et masquer les messages en fonction de l'état d'un objet clé/valeur qu'il écoute. Le La directive elle-même complète la notification des messages d'erreur avec ngModel Objet $ error (qui stocke un état clé/valeur des erreurs de validation).

Pour la validation de formulaire personnalisée, vous devez utiliser les modules ngMessages avec une directive personnalisée. Vous trouverez ici une validation simple qui vérifiera si la longueur du numéro est inférieure à 6 et afficher une erreur à l'écran.

 <form name="myform" novalidate>
                <table>
                    <tr>
                        <td><input name='test' type='text' required  ng-model='test' custom-validation></td>
                        <td ng-messages="myform.test.$error"><span ng-message="invalidshrt">Too Short</span></td>
                    </tr>
                </table>
            </form>

Voici comment créer une directive de validation personnalisée

angular.module('myApp',['ngMessages']);
        angular.module('myApp',['ngMessages']).directive('customValidation',function(){
            return{
            restrict:'A',
            require: 'ngModel',
            link:function (scope, element, attr, ctrl) {// 4th argument contain model information 

            function validationError(value) // you can use any function and parameter name 
                {
                 if (value.length > 6) // if model length is greater then 6 it is valide state
                 {
                 ctrl.$setValidity('invalidshrt',true);
                 }
                 else
                 {
                 ctrl.$setValidity('invalidshrt',false) //if less then 6 is invalide
                 }

                 return value; //return to display  error 
                }
                ctrl.$parsers.Push(validationError); //parsers change how view values will be saved in the model
            }
            };
        });

$setValidity est une fonction intégrée permettant de définir l'état du modèle sur valide/invalide 

3
Muhammad Nasir

J'ai étendu la réponse de @Ben Lesh avec la possibilité de spécifier si la validation est sensible à la casse ou non (par défaut)

utilisation:

<input type="text" name="fruitName" ng-model="data.fruitName" blacklist="Coconuts,Bananas,Pears" caseSensitive="true" required/>

code:

angular.module('crm.directives', []).
directive('blacklist', [
    function () {
        return {
            restrict: 'A',
            require: 'ngModel',
            scope: {
                'blacklist': '=',
            },
            link: function ($scope, $elem, $attrs, modelCtrl) {

                var check = function (value) {
                    if (!$attrs.casesensitive) {
                        value = (value && value.toUpperCase) ? value.toUpperCase() : value;

                        $scope.blacklist = _.map($scope.blacklist, function (item) {
                            return (item.toUpperCase) ? item.toUpperCase() : item
                        })
                    }

                    return !_.isArray($scope.blacklist) || $scope.blacklist.indexOf(value) === -1;
                }

                //For DOM -> model validation
                modelCtrl.$parsers.unshift(function (value) {
                    var valid = check(value);
                    modelCtrl.$setValidity('blacklist', valid);

                    return value;
                });
                //For model -> DOM validation
                modelCtrl.$formatters.unshift(function (value) {
                    modelCtrl.$setValidity('blacklist', check(value));
                    return value;
                });
            }
        };
    }
]);
1
Liran Brimer

Validations personnalisées appelant un serveur

Utilisez l'API ngModelController $asyncValidators , qui gère la validation asynchrone, telle que l'envoi d'une demande $http au système dorsal. Les fonctions ajoutées à l'objet doivent renvoyer une promesse qui doit être résolue si elle est valide ou rejetée si elle n'est pas valide. Les validations asynchrones en cours sont stockées par la clé dans ngModelController.$pending. Pour plus d'informations, voir Guide du développeur AngularJS - Formulaires (validation personnalisée) .

ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) {
  var value = modelValue || viewValue;

  // Lookup user by username
  return $http.get('/api/users/' + value).
     then(function resolved() {
       //username exists, this means validation fails
       return $q.reject('exists');
     }, function rejected() {
       //username does not exist, therefore this validation passes
       return true;
     });
};

Pour plus d'informations, voir 

1
georgeawg

Quelques excellents exemples et bibliothèques présentés dans ce fil, mais ils n’ont pas tout à fait ce que je cherchais. Mon approche: angular-valid - une bibliothèque de validation basée sur les promesses pour la validation asynchrone, avec le style optionnel Bootstrap cuit au four.

Une solution de validité angulaire pour le cas d'utilisation du PO pourrait ressembler à ceci:

<input  type="text" name="field4" ng-model="field4"
        validity="eval"
        validity-eval="!(field1 && field2 && field3 && !field4)"
        validity-message-eval="This field is required">

Voici un Fiddle , si vous voulez l'essayer. La lib est disponible sur GitHub , a une documentation détaillée et de nombreuses démos en direct.

0
2Toad