Come aggiungere la convalida personalizzata a un modulo AngularJS?


278

Ho un modulo con campi di input e configurazione di convalida aggiungendo gli requiredattributi e così via. Ma per alcuni campi ho bisogno di fare qualche validazione extra. Come potrei "attingere" alla convalida che FormControllercontrolla?

La convalida personalizzata potrebbe essere simile a "se questi 3 campi sono compilati, questo campo è obbligatorio e deve essere formattato in un modo particolare".

C'è un metodo FormController.$setValidityma non sembra un'API pubblica, quindi preferisco non usarlo. La creazione di una direttiva personalizzata e l'utilizzo NgModelControllersembra un'altra opzione, ma fondamentalmente mi richiederebbe di creare una direttiva per ogni regola di convalida personalizzata, che non desidero.

In realtà, contrassegnare un campo dal controller come non valido (pur rimanendo FormControllersincronizzato) potrebbe essere la cosa di cui ho bisogno nello scenario più semplice per fare il lavoro, ma non so come farlo.


4
C'è un bell'articolo sul mostro di codifica per gestire convalide personalizzate in JS angolare. Controllare questo fuori
Anshu

Non è esattamente quello che sto cercando, poiché richiede direttive personalizzate, ma accetterò la tua risposta poiché è comunque un buon articolo.
Botteaap,

Mi chiedo la stessa cosa, mi piacerebbe un po 'di controllo a livello di FormController. Ad esempio, desidero che determinate direttive personalizzate contrassegnino l'istanza di FormController come qualcosa del genere formName.$warning.
Adam Waselnuk,

2
Credo che $$precede le apis non pubbliche, $essendo pubbliche. Vedi stackoverflow.com/questions/19338493/…
Daniel F

Risposte:


370

Modifica: aggiunte informazioni su ngMessages (> = 1.3.X) di seguito.

Messaggi di convalida del modulo standard (1.0.X e successivi)

Dal momento che questo è uno dei migliori risultati se tu "Convalida della forma angolare" di Google, attualmente, voglio aggiungere un'altra risposta a questo per chiunque arrivi da lì.

C'è un metodo in FormController. $ SetValidity ma non sembra un'API pubblica, quindi preferisco non usarlo.

È "pubblico", nessuna preoccupazione. Usalo Ecco a cosa serve. Se non fosse stato progettato per essere utilizzato, gli sviluppatori angolari lo avrebbero privatizzato in una chiusura.

Per eseguire la convalida personalizzata, se non si desidera utilizzare l'interfaccia utente angolare come suggerito dall'altra risposta, è possibile semplicemente applicare la propria direttiva di convalida.

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

Ed ecco alcuni esempi di utilizzo:

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

Nota: in 1.2.X è probabilmente preferibile sostituire ng-ifquanto ng-showsopra

Ecco un link obbligatorio per il plunker

Inoltre, ho scritto alcuni articoli sul blog proprio su questo argomento che va un po 'più in dettaglio:

Convalida della forma angolare

Direttive di convalida personalizzate

Modifica: usando ngMessages in 1.3.X

Ora puoi usare il modulo ngMessages invece di ngShow per mostrare i tuoi messaggi di errore. Funzionerà effettivamente con qualsiasi cosa, non deve essere un messaggio di errore, ma ecco le basi:

  1. Includere <script src="angular-messages.js"></script>
  2. Riferimento ngMessagesnella dichiarazione del modulo:

    var app = angular.module('myApp', ['ngMessages']);
  3. Aggiungi il markup appropriato:

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

Nel markup di cui sopra, ng-message="personForm.email.$error"sostanzialmente specifica un contesto per le ng-messagedirettive figlio. Quindi ng-message="required"e ng-message="email"specificare le proprietà su quel contesto da guardare. Soprattutto, specificano anche un ordine per registrarli . Il primo che trova nella lista che è "vero" vince e mostrerà quel messaggio e nessuno degli altri.

E un plunker per l'esempio di ngMessages


6
Se si restituisce valore sulla funzione che si passa a $ parsers.unshift, anche i valori errati verranno salvati nel modello - sarebbe meglio restituire undefined, credo (quando il valore non è valido).
georgiosd

5
+1 @georgiosd ... 100% corretto. Guardando attraverso ciò che fa Angular, stanno tornando indefiniti. Probabilmente non è un grosso problema restituire il valore, dato che (si spera) modelli da moduli non validi non vengono inviati ... ma è meglio prevenire che curare, suppongo.
Ben Lesh,

2
Roba fantastica! Se hai cercato su Google di cercare un buon articolo sulla convalida personalizzata in Angular, controlla cosa ha scritto
@blesh

Hai controllato la validazione avanzata dei moduli con AngularJS e filtri ? Risolve genericamente la convalida del filtro.
Benny Bottema,

1
Penso che potresti aver voluto fare return value ? valid : undefinedsopra.
GChorn,

92

Il progetto di Angular-UI include una direttiva validata dall'UI, che probabilmente ti aiuterà in questo. Ti consente di specificare una funzione da chiamare per eseguire la convalida.

Dai un'occhiata alla pagina demo: http://angular-ui.github.com/ , cerca in basso fino alla voce Convalida.

Dalla pagina demo:

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

quindi nel controller:

function ValidateCtrl($scope) {
  $scope.blackList = ['bad@domain.com','verybad@domain.com'];
  $scope.notBlackListed = function(value) {
    return $scope.blackList.indexOf(value) === -1;
  };
}

Che strano che questo non funzioni per me usando Angular 1.4
Nick,

46

Puoi utilizzare ng-richiesto per il tuo scenario di validazione ("se questi 3 campi sono compilati, allora questo campo è obbligatorio":

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

2
Questo ha funzionato per me. Per convalide semplici che dipendono dai valori di altri campi, questa è la strada da percorrere invece di
scrivere

28

Puoi usare Angular-Validator .

Esempio: utilizzo di una funzione per convalidare un campo

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

Quindi nel tuo controller avresti qualcosa di simile

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

Puoi anche fare qualcosa del genere:

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

(dove field1 field2 e field3 sono variabili dell'ambito. È inoltre possibile verificare se i campi non corrispondono alla stringa vuota)

Se il campo non passa, il validatorcampo verrà contrassegnato come non valido e l'utente non sarà in grado di inviare il modulo.

Per altri casi d'uso ed esempi, consultare: https://github.com/turinggroup/angular-validator

Disclaimer: sono l'autore di Angular-Validator


13

Di recente ho creato una direttiva per consentire l'invalidazione basata su espressioni di input di forme angolari. È possibile utilizzare qualsiasi espressione angolare valida e supporta chiavi di convalida personalizzate utilizzando la notazione oggetto. Testato con angolare 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);
                    });
                }
            }
        };
    }]);

Puoi usarlo in questo modo:

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

O semplicemente passando un'espressione (verrà fornita la chiave di validazione predefinita di "invalidIf")

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

13

Ecco un ottimo modo per eseguire convalide di espressioni jolly personalizzate in un modulo (da: Convalida avanzata dei moduli con AngularJS e filtri ):

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

Demo jsFiddle (supporta la denominazione delle espressioni e le espressioni multiple)

È simile a ui-validate, ma non è necessaria una funzione di convalida specifica dell'ambito (funziona genericamente) e ovviamente non è necessario ui.utils in questo modo.


Grazie. Molto bello. È particolarmente utile applicare le regole di convalida per i moduli dinamici. Tuttavia, imposta comunque il valore del modello anche se non è valido. In ogni caso, per impedirlo, imposta modelValue se non è valido?
YuMei,

5

Aggiornare:

Versione migliorata e semplificata della direttiva precedente (una invece di due) con la stessa funzionalità:

.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();
                    });
                });
            }
        }
    };
}])

Esempio di utilizzo:

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

Risultato: espressioni di test reciprocamente dipendenti in cui i validatori vengono eseguiti in caso di modifica del modello di direttiva e del modello corrente di altri.

L'espressione di test ha una $modelvariabile locale che è necessario utilizzare per confrontarla con altre variabili.

In precedenza:

Ho tentato di migliorare il codice @Plantface aggiungendo ulteriori direttive. Questa direttiva aggiuntiva è molto utile se la nostra espressione deve essere eseguita quando vengono apportate modifiche in più di una variabile 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();
                });
            });
        }
    };
}])

Esempio su come utilizzarlo per creare campi convalidati in modo incrociato:

<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-expressionviene eseguito per convalidare il modello quando ng-modelo una qualsiasi delle ensure-watchvariabili viene modificata.


4

@synergetic Penso che @blesh supponga di mettere la funzione validata come di seguito

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

Convalide personalizzate che chiamano un server

Utilizzare l' API ngModelController$asyncValidators che gestisce la convalida asincrona, ad esempio effettuare una $httprichiesta al back-end. Le funzioni aggiunte all'oggetto devono restituire una promessa che deve essere risolta se valida o rifiutata quando non valida. Le convalide asincrone in corso vengono archiviate digitando ngModelController.$pending. Per ulteriori informazioni, consultare la Guida per gli sviluppatori di AngularJS - Moduli (convalida personalizzata) .

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

Per ulteriori informazioni, vedere


Utilizzando l' $validatorsAPI

La risposta accettata utilizza le pipeline $parserse $formattersper aggiungere un validatore sincrono personalizzato. AngularJS 1.3+ ha aggiunto $validatorsun'API, quindi non è necessario inserire validatori nelle pipeline $parserse $formatters:

app.directive('blacklist', function (){ 
   return {
      require: 'ngModel',
      link: function(scope, elem, attr, ngModel) {           
          ngModel.$validators.blacklist = function(modelValue, viewValue) {
              var blacklist = attr.blacklist.split(',');
              var value = modelValue || viewValue;
              var valid = blacklist.indexOf(value) === -1;
              return valid;
          });    
      }
   };
});

Per ulteriori informazioni, vedere Riferimento API di AngularJS ngModelController - $ validators .


3

In AngularJS il posto migliore per definire la convalida personalizzata è la direttiva Cutsom. AngularJS fornisce un modulo ngMessages.

ngMessages è una direttiva progettata per mostrare e nascondere i messaggi in base allo stato di un oggetto chiave / valore su cui è in ascolto. La direttiva stessa integra la segnalazione dei messaggi di errore con l'oggetto ngModel $ error (che memorizza uno stato chiave / valore degli errori di convalida).

Per la convalida del modulo personalizzato Uno dovrebbe usare i moduli ngMessages con direttiva personalizzata. Qui ho una semplice convalida che controllerà se la lunghezza del numero è inferiore a 6 visualizza un errore sullo schermo

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

Ecco come creare una direttiva di convalida personalizzata

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 è la funzione integrata per impostare lo stato del modello su valido / non valido


1

Ho esteso la risposta di @Ben Lesh con la possibilità di specificare se la convalida fa distinzione tra maiuscole e minuscole (impostazione predefinita)

uso:

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

codice:

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

0

Alcuni grandi esempi e librerie presentati in questo thread, ma non avevano proprio quello che stavo cercando. Il mio approccio: angular-validity - una libreria di convalida basata sulla promessa per la convalida asincrona, con lo stile Bootstrap opzionale integrato.

Una soluzione di validità angolare per il caso d'uso del PO potrebbe essere simile a questa:

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

Ecco un violino , se vuoi provarlo. La libreria è disponibile su GitHub , ha una documentazione dettagliata e molte demo dal vivo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.