Jak dodać niestandardową weryfikację do formularza AngularJS?

278

Mam formularz z polami wejściowymi i konfiguracją sprawdzania poprawności, dodając requiredatrybuty i takie. Ale w przypadku niektórych pól muszę przeprowadzić dodatkową weryfikację. Jak miałbym „włączyć się” do FormControllerkontroli, która kontroluje?

Weryfikacja niestandardowa może wyglądać tak: „jeśli te 3 pola są wypełnione, to pole jest wymagane i musi zostać sformatowane w określony sposób”.

Jest metoda, FormController.$setValidityale to nie wygląda na publiczny interfejs API, więc raczej go nie używam. Tworzenie niestandardowej dyrektywy i korzystanie z niej NgModelControllerwygląda jak inna opcja, ale zasadniczo wymagałoby ode mnie utworzenia dyrektywy dla każdej niestandardowej reguły sprawdzania poprawności, czego nie chcę.

Właściwie oznaczenie pola z kontrolera jako niepoprawnego (przy jednoczesnym utrzymaniu FormControllersynchronizacji) może być rzeczą, której potrzebuję w najprostszym scenariuszu, aby wykonać zadanie, ale nie wiem, jak to zrobić.

botteaap
źródło
4
Jest ładny artykuł na temat kodowania potwora do obsługi niestandardowych walidacji w kątowym JS. Sprawdzić to out
Anshu
Nie jest to dokładnie to, czego szukam, ponieważ wymaga niestandardowych dyrektyw, ale zaakceptuję twoją odpowiedź, ponieważ i tak jest to dobry artykuł.
botteaap
Zastanawiam się nad tym samym, chciałbym mieć kontrolę na poziomie FormController. Na przykład chcę, aby niektóre niestandardowe dyrektywy oflagowały instancję FormController jako coś podobnego formName.$warning.
Adam Waselnuk
2
Wierzę, że $$poprzedza niepubliczne apis, $będąc publicznym. Zobacz stackoverflow.com/questions/19338493/…
Daniel F

Odpowiedzi:

370

Edycja: dodano informacje o ngMessages (> = 1.3.X) poniżej.

Standardowe komunikaty sprawdzania poprawności formularza (1.0.X i nowsze)

Ponieważ jest to jeden z najlepszych wyników, jeśli Google „Angular Form Validation”, obecnie chcę dodać kolejną odpowiedź na to pytanie dla każdego, kto stamtąd przyjdzie.

FormController zawiera metodę. $ SetValidity, ale nie wygląda to na publiczny interfejs API, więc raczej go nie używam.

To „publiczne”, bez obaw. Użyj tego. Po to jest. Gdyby nie miał być użyty, deweloperzy Angular sprywatyzowaliby go w zamknięciu.

Aby przeprowadzić niestandardową weryfikację, jeśli nie chcesz używać Angular-UI jako drugiej sugerowanej odpowiedzi, możesz po prostu stworzyć własną dyrektywę sprawdzania poprawności.

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

A oto przykładowe użycie:

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

Uwaga: w 1.2.x to chyba preferrable zastąpił ng-ifna ng-showwyżej

Oto obowiązkowy link plunker

Napisałem też kilka wpisów na blogu na ten temat, który zawiera trochę więcej szczegółów:

Walidacja formy kątowej

Niestandardowe dyrektywy walidacyjne

Edycja: za pomocą ngMessages w 1.3.X

Możesz teraz używać modułu ngMessages zamiast ngShow, aby wyświetlać komunikaty o błędach. W rzeczywistości będzie działać z czymkolwiek, nie musi to być komunikat o błędzie, ale oto podstawy:

  1. Zawierać <script src="angular-messages.js"></script>
  2. Odniesienie ngMessagesw deklaracji modułu:

    var app = angular.module('myApp', ['ngMessages']);
  3. Dodaj odpowiedni znacznik:

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

W powyższym znaczniku w ng-message="personForm.email.$error"zasadzie określa kontekst dla ng-messagedyrektyw potomnych. Wtedy ng-message="required"i ng-message="email"określić właściwości w tym kontekście do oglądania.Co najważniejsze, określają również kolejność ich zameldowania . Pierwszy, który znajdzie na liście, który jest „prawdomówny”, wygrywa i pokaże tę wiadomość i żaden z pozostałych.

I plunker dla przykładu ngMessages

Ben Lesh
źródło
6
Jeśli zwrócisz wartość funkcji, którą przekazujesz do $ parsers.unshift, błędne wartości zostaną również zapisane w modelu - lepiej, jak sądzę, zwrócenie niezdefiniowanej wartości (gdy wartość jest nieprawidłowa).
georgiosd
5
+1 @georgiosd ... 100% poprawności. Patrząc na to, co robi Angular, wracają niezdefiniowane. Zwrócenie wartości prawdopodobnie nie jest wielkim problemem, ponieważ (mam nadzieję) modele z nieprawidłowych formularzy nie zostały przesłane ... ale chyba bezpieczniejsze niż przepraszam.
Ben Lesh
2
Świetne rzeczy! Jeśli znalazłeś się w Google, szukając dobrego zapisu na temat niestandardowej weryfikacji w Angular, sprawdź, co napisał
@ rozwiązywania problemów
Czy sprawdziłeś Zaawansowane sprawdzanie poprawności formularza za pomocą AngularJS i filtrów ? Generalnie rozwiązuje sprawdzanie poprawności filtra.
Benny Bottema
1
Myślę, że mogłeś to zrobić return value ? valid : undefinedpowyżej.
GChorn
92

Projekt Angular-UI zawiera dyrektywę sprawdzającą poprawność interfejsu użytkownika, która prawdopodobnie Ci w tym pomoże. Pozwala określić funkcję, która ma zostać wywołana w celu sprawdzenia poprawności.

Zajrzyj na stronę demonstracyjną: http://angular-ui.github.com/ , do nagłówka Sprawdź poprawność.

Ze strony demonstracyjnej:

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

następnie w kontrolerze:

function ValidateCtrl($scope) {
  $scope.blackList = ['[email protected]','[email protected]'];
  $scope.notBlackListed = function(value) {
    return $scope.blackList.indexOf(value) === -1;
  };
}
Pete BD
źródło
Jak dziwne, że to nie działa dla mnie przy użyciu Angulara 1.4
Nick
46

W scenariuszu sprawdzania poprawności możesz użyć ng-wymagane („jeśli te 3 pola są wypełnione, to pole jest wymagane”):

<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>
Mario G.
źródło
2
To zadziałało dla mnie. W przypadku prostych sprawdzeń poprawności, które zależą od wartości innych pól, jest to sposób, aby przejść zamiast
pisania
28

Możesz użyć Angular-Validator .

Przykład: użycie funkcji do sprawdzenia poprawności pola

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

Wtedy w twoim kontrolerze będziesz mieć coś takiego

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

Możesz także zrobić coś takiego:

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

(gdzie field1 field2 i field3 są zmiennymi zakresu. Możesz również sprawdzić, czy pola nie są równe pustemu ciągowi)

Jeśli pole nie przejdzie, validatorpole zostanie oznaczone jako nieprawidłowe i użytkownik nie będzie mógł przesłać formularza.

Więcej przypadków użycia i przykładów można znaleźć na stronie : https://github.com/turinggroup/angular-validator

Oświadczenie: Jestem autorem Angular-Validator

użytkownik3920706
źródło
13

Niedawno stworzyłem dyrektywę, która pozwala na oparte na wyrażeniach unieważnianie danych kątowych. Można użyć dowolnego poprawnego wyrażenia kątowego i obsługuje on niestandardowe klucze sprawdzania poprawności za pomocą notacji obiektowej. Testowany z kątowym 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);
                    });
                }
            }
        };
    }]);

Możesz użyć tego w następujący sposób:

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

Lub po prostu przekazując wyrażenie (otrzyma domyślny klucz validationKey z „invalidIf”)

<input ng-model="foo" invalid-if="foo > bar"/>
Alex Schwartz
źródło
13

Oto fajny sposób na wykonanie niestandardowej weryfikacji wyrażeń wieloznacznych w formularzu (z: Zaawansowana walidacja formularza za pomocą AngularJS i filtrów ):

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

Wersja demonstracyjna jsFiddle (obsługuje nazewnictwo wyrażeń i wiele wyrażeń)

Jest podobny do ui-validate, ale nie potrzebujesz funkcji sprawdzania poprawności określonego zakresu (działa to ogólnie) i oczywiście nie potrzebujesz w ten sposób pliku ui.utils .

Benny Bottema
źródło
Dzięki. Bardzo fajny. Szczególnie przydatne jest stosowanie reguł sprawdzania poprawności formularzy dynamicznych. Jednak nadal ustawia wartość modelu, nawet jeśli jest niepoprawna. W każdym razie, aby temu zapobiec, ustaw modelValue, jeśli jest nieprawidłowy?
YuMei,
5

Aktualizacja:

Ulepszona i uproszczona wersja poprzedniej dyrektywy (jedna zamiast dwóch) o tej samej funkcjonalności:

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

Przykładowe użycie:

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

Wynik: wzajemnie zależne wyrażenia testowe, w których walidatory są wykonywane po zmianie modelu dyrektywy innego i bieżącego modelu.

Wyrażenie testowe ma $modelzmienną lokalną , której należy użyć do porównania z innymi zmiennymi.

Poprzednio:

Podjąłem próbę ulepszenia kodu @Plantface, dodając dodatkową dyrektywę. Ta dodatkowa dyrektywa jest bardzo przydatna, jeśli nasze wyrażenie musi zostać wykonane, gdy zmiany zostaną wprowadzone w więcej niż jednej zmiennej 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();
                });
            });
        }
    };
}])

Przykład użycia go do tworzenia pól walidowanych krzyżowo:

<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-expressionjest wykonywany w celu sprawdzenia poprawności modelu, gdy zmienna ng-modellub ensure-watchzmienna jest zmienna.

knr
źródło
4

@synergetic Myślę, że @ rozwiążmy przypuszczenie, aby ustawić funkcję sprawdzania poprawności, jak poniżej

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);
Atul Chaudhary
źródło
4

Weryfikacje niestandardowe wywołujące serwer

Użyj interfejsu API ngModelController,$asyncValidators który obsługuje asynchroniczne sprawdzanie poprawności, na przykład wysyłanie żądania do wewnętrznej bazy danych$http . Funkcje dodane do obiektu muszą zwrócić obietnicę, która musi zostać rozwiązana, gdy jest ważna, lub odrzucona, gdy jest niepoprawna. Walidacje asynchroniczne w toku są zapisywane według klucza ngModelController.$pending. Aby uzyskać więcej informacji, zobacz AngularJS Developer Guide - Forms (Custom Validation) .

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

Aby uzyskać więcej informacji, zobacz


Korzystanie z $validatorsinterfejsu API

Zaakceptowana odpowiedź używa potoków $parsersi, $formattersaby dodać niestandardowy walidator synchroniczny. AngularJS 1.3+ dodał $validatorsAPI, więc nie ma potrzeby umieszczania walidatorów w potokach $parsersi $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;
          });    
      }
   };
});

Aby uzyskać więcej informacji, zobacz AngularJS ngModelController API Reference - $ validators .

georgeawg
źródło
3

W AngularJS najlepszym miejscem do zdefiniowania niestandardowej weryfikacji jest dyrektywa Cutsom. AngularJS zapewnia moduł ngMessages.

ngMessages to dyrektywa, której zadaniem jest wyświetlanie i ukrywanie wiadomości na podstawie stanu obiektu klucz / wartość, na którym nasłuchuje. Sama dyrektywa uzupełnia raportowanie komunikatów o błędach o obiekt błędu ngModel $ (który przechowuje stan klucza / wartości błędów sprawdzania poprawności).

Do sprawdzania poprawności formularzy należy używać modułów ngMessages z niestandardową dyrektywą. Tutaj mam prostą weryfikację, która sprawdzi, czy długość numeru jest mniejsza niż 6, wyświetla błąd na ekranie

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

Oto jak utworzyć niestandardową dyrektywę sprawdzania poprawności

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 jest wbudowaną funkcją ustawiania stanu modelu na poprawny / nieprawidłowy

Muhammad Nasir
źródło
1

Rozszerzyłem odpowiedź @Ben Lesh z możliwością określenia, czy w sprawdzaniu poprawności rozróżniana jest wielkość liter, czy nie (domyślnie)

posługiwać się:

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

kod:

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;
                });
            }
        };
    }
]);
Liran Brimer
źródło
0

Kilka świetnych przykładów i bibliotek prezentowanych w tym wątku, ale nie do końca miały to, czego szukałem. Moje podejście: ważność kątowa - biblioteka sprawdzania poprawności oparta na obietnicach dla sprawdzania poprawności asynchronicznej, z opcjonalnym wbudowanym stylem Bootstrap.

Rozwiązanie sprawdzania poprawności kątowej dla przypadku użycia OP może wyglądać mniej więcej tak:

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

Oto skrzypce , jeśli chcesz spróbować. Biblioteka jest dostępna na GitHub , ma szczegółową dokumentację i mnóstwo demonstracji na żywo.

2 Załaduj
źródło