Как добавить пользовательскую проверку в форму AngularJS?

278

У меня есть форма с полями ввода и настройки проверки путем добавления requiredатрибутов и тому подобное. Но для некоторых полей мне нужно сделать дополнительную проверку. Как бы я «подключился» к проверке, которая FormControllerконтролирует?

Пользовательская проверка может выглядеть примерно так: «если эти 3 поля заполнены, то это поле обязательно для заполнения и должно быть отформатировано определенным образом».

Есть метод, FormController.$setValidityно он не похож на публичный API, поэтому я не буду его использовать. Создание пользовательской директивы и ее использование NgModelControllerвыглядит как еще один вариант, но в основном мне потребуется создать директиву для каждого пользовательского правила проверки, что мне не нужно.

На самом деле, пометка поля из контроллера как недействительного (хотя и FormControllerс синхронизацией) может быть тем, что мне нужно в простейшем сценарии для выполнения работы, но я не знаю, как это сделать.

botteaap
источник
4
Есть хорошая статья о кодировании монстра для обработки пользовательских проверок в angular JS. Проверьте это аут
Anshu
Это не совсем то, что я ищу, так как для этого требуются пользовательские директивы, но я приму ваш ответ, так как в любом случае это хорошая статья.
botteaap
Мне интересно то же самое, я хотел бы некоторый контроль на уровне FormController. Например, я хочу, чтобы определенные пользовательские директивы помечали экземпляр FormController как-то так formName.$warning.
Адам Васельнюк
2
Я считаю, что $$предшествует публичный apis, с $публичностью. См. Stackoverflow.com/questions/19338493/…
Даниэль Ф

Ответы:

370

Изменить: добавлена ​​информация о ngMessages (> = 1.3.X) ниже.

Стандартные сообщения проверки формы (1.0.X и выше)

Поскольку это один из лучших результатов, если вы пользуетесь Google «Проверка угловых форм», в настоящее время я хочу добавить еще один ответ на этот вопрос для всех, кто придет оттуда.

В FormController есть метод. $ SetValidity, но он не похож на публичный API, поэтому я не буду его использовать.

Это "публика", не беспокойтесь. Используй это. Вот для чего это. Если бы это не было предназначено, разработчики Angular приватизировали бы его в закрытом состоянии.

Чтобы выполнить пользовательскую проверку, если вы не хотите использовать Angular-UI, как предлагает другой ответ, вы можете просто свернуть свою собственную директиву проверки.

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

А вот пример использования:

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

Примечание: в 1.2.X это, вероятно , preferrable заменить ng-ifна ng-showвыше

Вот обязательная плункерная ссылка

Кроме того, я написал несколько записей в блоге только на эту тему, которые более подробно рассматриваются:

Проверка угловых форм

Пользовательские директивы валидации

Редактировать: используя ngMessages в 1.3.X

Теперь вы можете использовать модуль ngMessages вместо ngShow для отображения ваших сообщений об ошибках. На самом деле он будет работать с чем угодно, это не должно быть сообщением об ошибке, но вот основы:

  1. Включают <script src="angular-messages.js"></script>
  2. Ссылка ngMessagesв декларации вашего модуля:

    var app = angular.module('myApp', ['ngMessages']);
  3. Добавьте соответствующую разметку:

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

В приведенной выше разметке в ng-message="personForm.email.$error"основном указывается контекст для ng-messageдочерних директив. Затем ng-message="required"и ng-message="email"укажите свойства в этом контексте для просмотра. Самое главное, они также указывают порядок их регистрации . Первый, который он находит в списке «правдивых», побеждает, и он покажет это сообщение, и ни один из остальных.

И плункер для примера ngMessages

Бен Леш
источник
6
Если вы вернете значение функции, которую вы передадите в $ parsers.unshift, ошибочные значения также будут сохранены в модели - было бы лучше вернуть неопределенное, как я полагаю (когда значение недопустимо).
Георгиосд
5
+1 @georgiosd ... 100% правильно. Просматривая, что делает Angular, они возвращают неопределенное. Возвращать значение, вероятно, не составляет особого труда, поскольку (надеюсь), что модели из недопустимых форм не отправляются ... но лучше, чем потом сожалеть, я полагаю.
Бен Леш
2
Отличный материал! Если вы гуглили свой путь здесь в поисках хорошей рецензии на пользовательскую проверку в Angular, посмотрите, что написал
@blesh
Вы проверяли расширенную проверку формы с AngularJS и фильтрами ? Это решает проверку фильтра в общем.
Бенни Боттема
1
Я думаю, что вы, возможно, хотели сделать return value ? valid : undefinedвыше.
GChorn
92

Проект Angular-UI включает в себя директиву ui-validate, которая, вероятно, поможет вам в этом. Это позволяет вам указать функцию для вызова, чтобы сделать проверку.

Взгляните на демонстрационную страницу: http://angular-ui.github.com/ , найдите вниз до заголовка Validate.

Со страницы демо:

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

тогда в вашем контроллере:

function ValidateCtrl($scope) {
  $scope.blackList = ['bad@domain.com','verybad@domain.com'];
  $scope.notBlackListed = function(value) {
    return $scope.blackList.indexOf(value) === -1;
  };
}
Пит Б.Д.
источник
Как странно, что это не работает для меня, используя Angular 1.4
Ник
46

Вы можете использовать ng-required для вашего сценария проверки («если эти 3 поля заполнены, то это поле обязательно»:

<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
Это сработало для меня. Для простых проверок, которые зависят от значений других полей, вместо написания сложных правил
проверок следует придерживаться этого
28

Вы можете использовать Angular-Validator .

Пример: использование функции для проверки поля

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

Тогда в вашем контроллере у вас будет что-то вроде

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

Вы также можете сделать что-то вроде этого:

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

(где field1 field2 и field3 - переменные области видимости. Вы также можете проверить, не равняются ли поля пустой строке)

Если поле не проходит, validatorто поле будет помечено как недействительное, и пользователь не сможет отправить форму.

Дополнительные примеры использования и примеры см .: https://github.com/turinggroup/angular-validator.

Отказ от ответственности: я автор Angular-Validator

user3920706
источник
13

Недавно я создал директиву, позволяющую делать недействительными основанные на выражениях вводы угловых форм. Можно использовать любое допустимое угловое выражение, и оно поддерживает пользовательские ключи проверки с использованием нотации объекта. Протестировано с угловой 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);
                    });
                }
            }
        };
    }]);

Вы можете использовать это так:

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

Или просто передавая выражение (ему будет присвоен ключ validationKey по умолчанию "invalidIf")

<input ng-model="foo" invalid-if="foo > bar"/>
Алекс Шварц
источник
13

Вот крутой способ сделать пользовательские проверки шаблонных выражений в форме (из: Расширенная проверка формы с AngularJS и фильтрами ):

<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 (поддерживает именование выражений и множественные выражения)

Это похоже на ui-validate, но вам не нужна специфическая для области функция проверки (это работает в общем) и, конечно, вам не нужен ui.utils таким образом.

Бенни Боттема
источник
Спасибо. Очень круто. Особенно полезно применять правила проверки для динамических форм. Тем не менее, он по-прежнему устанавливает значение модели, даже если он недействителен. В любом случае, чтобы предотвратить это, установите modelValue, если он недействителен?
YuMei
5

Обновить:

Улучшенная и упрощенная версия предыдущей директивы (одна вместо двух) с той же функциональностью:

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

Пример использования:

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

Результат: взаимозависимые тестовые выражения, в которых валидаторы выполняются при изменении другой директивной модели и текущей модели.

Тестовое выражение имеет локальную $modelпеременную, которую следует использовать для сравнения с другими переменными.

Ранее:

Я попытался улучшить код @Plantface, добавив дополнительную директиву. Эта дополнительная директива очень полезна, если наше выражение необходимо выполнить, когда изменения сделаны в более чем одной переменной 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();
                });
            });
        }
    };
}])

Пример того, как использовать его для создания перекрестных проверенных полей:

<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выполняется для проверки модели при ng-modelизменении любой из ensure-watchпеременных.

KNR
источник
4

@synergetic Я думаю, что @blesh положит функцию валидации, как показано ниже

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

Пользовательские проверки, которые вызывают сервер

Используйте API ngModelController,$asyncValidators который обрабатывает асинхронную проверку, например, делает $httpзапрос к бэкэнду. Функции, добавленные к объекту, должны возвращать обещание, которое должно быть разрешено, если оно допустимо, или отклонено, если оно недействительно. Выполняемые асинхронные проверки сохраняются ключом ngModelController.$pending. Для получения дополнительной информации см. 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;
     });
};

Для получения дополнительной информации см.


Использование $validatorsAPI

Принятый ответ использует $parsersи$formatters трубопроводы для добавления пользовательского синхронного валидатора. AngularJS 1.3+ добавил $validatorsAPI , так что нет необходимости ставить валидатор в $parsersи $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;
          });    
      }
   };
});

Для получения дополнительной информации посмотрите Ссылку API AngularJS ngModelController - $ validators .

georgeawg
источник
3

В AngularJS лучшее место для определения пользовательской проверки - это директива Cutsom. AngularJS предоставляет модуль ngMessages.

ngMessages - это директива, предназначенная для отображения и скрытия сообщений в зависимости от состояния объекта «ключ / значение», который он прослушивает. Сама директива дополняет сообщение об ошибке сообщением объекта ошибки ngModel $ (в котором хранится состояние ключ / значение ошибок проверки).

Для проверки пользовательской формы следует использовать модули ngMessages с пользовательской директивой. Здесь у меня есть простая проверка, которая проверит, если длина номера меньше 6, отобразит ошибку на экране

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

Вот как создать пользовательскую директиву проверки

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 встроенная функция для установки состояния модели на действительное / недействительное

Мухаммед Насир
источник
1

Я расширил ответ @Ben Lesh, указав, чувствительна ли к регистру проверка (по умолчанию)

использовать:

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

код:

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

В этой ветке представлено несколько отличных примеров и библиотек, но у них не было того, что я искал. Мой подход: angular-validity - библиотека валидации на основе обещаний для асинхронной валидации с опциональным встроенным стилем Bootstrap.

Решение по угловой точности для варианта использования OP может выглядеть примерно так:

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

Вот скрипка , если вы хотите взять ее на спин. Библиотека доступна на GitHub , имеет подробную документацию и множество живых демонстраций.

2Toad
источник