Wie füge ich einem AngularJS-Formular eine benutzerdefinierte Validierung hinzu?


278

Ich habe ein Formular mit Eingabefeldern und Validierungs-Setup durch Hinzufügen der requiredAttribute und dergleichen. Aber für einige Felder muss ich eine zusätzliche Validierung durchführen. Wie würde ich auf die Validierung "tippen", die FormControllersteuert?

Benutzerdefinierte Validierung kann etwa so aussehen: "Wenn diese drei Felder ausgefüllt sind, ist dieses Feld erforderlich und muss auf eine bestimmte Weise formatiert werden."

Es gibt eine Methode FormController.$setValidity, die aber nicht wie eine öffentliche API aussieht, also verwende ich sie lieber nicht. Das Erstellen einer benutzerdefinierten Direktive und deren Verwendung NgModelControllersieht nach einer anderen Option aus, erfordert jedoch grundsätzlich das Erstellen einer Direktive für jede benutzerdefinierte Validierungsregel, die ich nicht möchte.

Das Markieren eines Felds vom Controller als ungültig (bei gleichzeitiger FormControllerSynchronisierung) ist möglicherweise das, was ich im einfachsten Szenario benötige, um die Aufgabe zu erledigen, aber ich weiß nicht, wie ich das tun soll.


4
Es gibt einen schönen Artikel über das Codieren von Monstern für die Handhabung von benutzerdefinierten Validierungen in eckigem JS. Überprüfen Sie dieses heraus
Anshu

Es ist nicht genau das, wonach ich suche, da es benutzerdefinierte Anweisungen erfordert, aber ich werde Ihre Antwort akzeptieren, da es sowieso ein guter Artikel ist.
Botteaap

Ich frage mich das Gleiche, ich würde gerne etwas Kontrolle auf der Ebene des FormControllers haben. Ich möchte beispielsweise, dass bestimmte benutzerdefinierte Anweisungen die FormController-Instanz als so etwas wie kennzeichnen formName.$warning.
Adam Waselnuk

2
Ich glaube, das $$geht einer nicht öffentlichen Apis voraus, $wenn man öffentlich ist. Siehe stackoverflow.com/questions/19338493/…
Daniel F

Antworten:


370

Bearbeiten: Informationen zu ngMessages (> = 1.3.X) wurden hinzugefügt.

Standardnachrichten zur Formularüberprüfung (1.0.X und höher)

Da dies eines der Top-Ergebnisse ist, wenn Sie Google "Angular Form Validation" verwenden, möchte ich derzeit eine weitere Antwort für alle hinzufügen, die von dort kommen.

In FormController gibt es eine Methode. $ SetValidity, die jedoch nicht wie eine öffentliche API aussieht, daher verwende ich sie lieber nicht.

Es ist "öffentlich", keine Sorge. Benutze es. Dafür ist es da. Wenn es nicht verwendet werden sollte, hätten die Angular-Entwickler es in einer Schließung privatisiert.

Wenn Sie für die benutzerdefinierte Validierung Angular-UI nicht wie in der anderen vorgeschlagenen Antwort verwenden möchten, können Sie einfach Ihre eigene Validierungsanweisung erstellen.

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

Und hier ist ein Beispiel für die Verwendung:

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

Hinweis: in 1.2.X ist es wahrscheinlich preferrable Ersatz ng-iffür ng-showoben

Hier ist ein obligatorischer Plunker-Link

Außerdem habe ich ein paar Blogeinträge zu diesem Thema geschrieben, die etwas detaillierter sind:

Validierung der Winkelform

Benutzerdefinierte Validierungsanweisungen

Bearbeiten: Verwenden von ngMessages in 1.3.X.

Sie können jetzt das ngMessages-Modul anstelle von ngShow verwenden, um Ihre Fehlermeldungen anzuzeigen. Es wird tatsächlich mit allem funktionieren, es muss keine Fehlermeldung sein, aber hier sind die Grundlagen:

  1. Einschließen <script src="angular-messages.js"></script>
  2. Referenz ngMessagesin Ihrer Moduldeklaration:

    var app = angular.module('myApp', ['ngMessages']);
  3. Fügen Sie das entsprechende Markup hinzu:

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

Gibt im obigen Markup ng-message="personForm.email.$error"grundsätzlich einen Kontext für die ng-messageuntergeordneten Anweisungen an. Dann ng-message="required"und ng-message="email"geben Sie die Angebote in diesem Zusammenhang auf die Uhr. Am wichtigsten ist, dass sie auch eine Reihenfolge angeben, in der sie eingecheckt werden sollen . Der erste, der in der Liste "wahr" gefunden wird, gewinnt, und er zeigt diese Nachricht und keine der anderen.

Und ein Plunker für das Beispiel ngMessages


6
Wenn Sie einen Wert für die Funktion zurückgeben, die Sie an $ parsers.unshift übergeben, werden auch fehlerhafte Werte im Modell gespeichert. Ich glaube, es ist besser, undefiniert zurückzugeben (wenn der Wert nicht gültig ist).
Georgiosd

5
+1 @georgiosd ... 100% korrekt. Wenn sie sich ansehen, was Angular tut, kehren sie undefiniert zurück. Es ist wahrscheinlich keine große Sache, den Wert zurückzugeben, da (hoffentlich) Modelle aus ungültigen Formularen nicht eingereicht werden ... aber besser sicher als leid, nehme ich an.
Ben Lesh

2
Tolles Zeug! Wenn Sie Ihren Weg hierher gegoogelt haben und nach einer guten Beschreibung der benutzerdefinierten Validierung in Angular suchen, lesen Sie, was @blesh geschrieben hat
maaachine

Haben Sie die erweiterte Formularvalidierung mit AngularJS und Filtern überprüft ? Es löst die Filtervalidierung generisch.
Benny Bottema

1
Ich denke, Sie haben vielleicht vorgehabt, return value ? valid : undefinedoben zu tun .
GChorn

92

Das Projekt von Angular-UI enthält eine UI-Validierungsanweisung, die Ihnen wahrscheinlich dabei helfen wird. Hier können Sie eine Funktion angeben, die aufgerufen werden soll, um die Validierung durchzuführen.

Schauen Sie sich die Demoseite an : http://angular-ui.github.com/ , suchen Sie bis zur Überschrift Validieren.

Von der Demoseite:

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

dann in Ihrem Controller:

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

Wie seltsam, dass dies bei Angular 1.4 nicht funktioniert
Nick

46

Sie können ng-require für Ihr Validierungsszenario verwenden ("Wenn diese 3 Felder ausgefüllt sind, ist dieses Feld erforderlich":

<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
Das hat bei mir funktioniert. Für einfache Validierungen, die von anderen Feldwerten abhängen, ist dies der richtige Weg, anstatt komplexe Validierungsregeln zu
schreiben

28

Sie können Angular-Validator verwenden .

Beispiel: Verwenden einer Funktion zum Überprüfen eines Felds

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

Dann hätten Sie in Ihrem Controller so etwas wie

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

Sie können auch so etwas tun:

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

(wobei Feld1, Feld2 und Feld3 Bereichsvariablen sind. Möglicherweise möchten Sie auch überprüfen, ob die Felder nicht der leeren Zeichenfolge entsprechen.)

Wenn das Feld die nicht besteht validator wird das Feld als ungültig markiert und der Benutzer kann das Formular nicht senden.

Weitere Anwendungsfälle und Beispiele finden Sie unter: https://github.com/turinggroup/angular-validator

Haftungsausschluss: Ich bin der Autor von Angular-Validator


13

Ich habe kürzlich eine Direktive erstellt, um die ausdrucksbasierte Ungültigmachung von Winkelformulareingaben zu ermöglichen. Jeder gültige Winkelausdruck kann verwendet werden und unterstützt benutzerdefinierte Validierungsschlüssel in Objektnotation. Getestet mit Angular 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);
                    });
                }
            }
        };
    }]);

Sie können es so verwenden:

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

Oder indem Sie einfach einen Ausdruck übergeben (er erhält den Standardvalidierungsschlüssel "invalidIf").

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

13

Hier ist eine coole Möglichkeit, benutzerdefinierte Platzhalterausdrucksüberprüfungen in einem Formular durchzuführen (von: Erweiterte Formularüberprüfung mit AngularJS und Filtern ):

<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 (unterstützt die Benennung von Ausdrücken und mehrere Ausdrücke)

Es ist ähnlich wie ui-validate, aber Sie benötigen keine bereichsspezifische Validierungsfunktion (dies funktioniert generisch) und natürlich benötigen Sie auf diese Weise keine ui.utils .


Vielen Dank. Sehr cool. Es ist besonders nützlich, Validierungsregeln für dynamische Formulare anzuwenden. Der Modellwert wird jedoch weiterhin festgelegt, auch wenn er ungültig ist. Wie auch immer, um zu verhindern, dass der modelValue gesetzt wird, wenn er ungültig ist?
YuMei

5

Aktualisieren:

Verbesserte und vereinfachte Version der vorherigen Direktive (eine statt zwei) mit derselben Funktionalität:

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

Anwendungsbeispiel:

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

Ergebnis: Gegenseitig abhängige Testausdrücke, bei denen Validatoren ausgeführt werden, wenn das Direktivenmodell und das aktuelle Modell anderer geändert werden.

Der Testausdruck verfügt über eine lokale $modelVariable, mit der Sie ihn mit anderen Variablen vergleichen sollten.

Vorher:

Ich habe versucht, @ Plantface-Code durch Hinzufügen einer zusätzlichen Direktive zu verbessern. Diese zusätzliche Anweisung ist sehr nützlich, wenn unser Ausdruck ausgeführt werden muss, wenn Änderungen an mehr als einer ngModel-Variablen vorgenommen werden.

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

Beispiel für die Verwendung von kreuzvalidierten Feldern:

<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-expressionwird ausgeführt, um das Modell zu validieren, wenn ng-modeloder eine der ensure-watchVariablen geändert wird.


4

@synergetic Ich denke, @blesh nimmt an, die Funktionsvalidierung wie folgt zu setzen

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

Benutzerdefinierte Überprüfungen, die einen Server aufrufen

Verwenden Sie die ngModelController- $asyncValidatorsAPI, die die asynchrone Validierung übernimmt, z. B. eine $httpAnforderung an das Backend. Dem Objekt hinzugefügte Funktionen müssen ein Versprechen zurückgeben, das bei Gültigkeit aufgelöst oder bei Ungültigkeit abgelehnt werden muss. In Bearbeitung befindliche asynchrone Überprüfungen werden durch Eingabe gespeichert ngModelController.$pending. Weitere Informationen finden Sie im AngularJS-Entwicklerhandbuch - Formulare (benutzerdefinierte Validierung) .

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

Weitere Informationen finden Sie unter


Verwenden der $validatorsAPI

Die akzeptierte Antwort verwendet das $parsersund$formatters Pipelines , um einen benutzerdefinierten synchronen Validator hinzuzufügen. AngularJS 1.3+ hat eine $validatorsAPI hinzugefügt, sodass keine Validatoren in die $parsersund -Pipelines eingefügt werden müssen $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;
          });    
      }
   };
});

Weitere Informationen finden Sie unter AngularJS ngModelController-API-Referenz - $ validators .


3

In AngularJS ist die Cutsom-Direktive der beste Ort, um die benutzerdefinierte Validierung zu definieren. AngularJS bietet ein ngMessages-Modul.

ngMessages ist eine Direktive, mit der Nachrichten basierend auf dem Status eines Schlüssel- / Wertobjekts, das sie abhört, ein- und ausgeblendet werden. Die Direktive selbst ergänzt die Fehlermeldung mit dem ngModel $ -Fehlerobjekt (das einen Schlüssel- / Wertstatus von Validierungsfehlern speichert).

Für die benutzerdefinierte Formularvalidierung sollte man ngMessages-Module mit benutzerdefinierter Direktive verwenden. Hier habe ich eine einfache Validierung, die überprüft, ob die Nummernlänge kleiner als 6 ist, und einen Fehler auf dem Bildschirm anzeigt

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

Hier erfahren Sie, wie Sie eine benutzerdefinierte Validierungsanweisung erstellen

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 ist eine eingebaute Funktion, um den Modellstatus auf gültig / ungültig zu setzen


1

Ich habe die Antwort von @Ben Lesh um die Möglichkeit erweitert, anzugeben, ob bei der Validierung zwischen Groß- und Kleinschreibung unterschieden wird oder nicht (Standard).

verwenden:

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

0

Einige großartige Beispiele und Bibliotheken, die in diesem Thread vorgestellt wurden, aber sie hatten nicht ganz das, wonach ich suchte. Mein Ansatz: Winkelvalidität - eine vielversprechende Validierungsbibliothek für die asynchrone Validierung mit integriertem optionalem Bootstrap-Styling.

Eine Winkelvaliditätslösung für den Anwendungsfall des OP könnte ungefähr so ​​aussehen:

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

Hier ist eine Geige , wenn Sie sie ausprobieren möchten. Die Bibliothek ist auf GitHub verfügbar , verfügt über eine detaillierte Dokumentation und zahlreiche Live-Demos.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.