AngularJS и проверка данных формы

AngularJS Validation

Пост состоит из следующих частей:

  • Проверка данных в HTML5
  • AngularJS расширения для валидации
  • Свои кастомные проверки данных на AngularJS
  • Вывод сообщений об ошибках и ng-messages

 

HTML5 валидация данных

В первую очередь следует отметить, что у тега <form> в HTML5 появился атрибут novalidate, который говорит браузеру, что форму валидировать не нужно.

Вспомогательные атрибуты проверки данных:

  • require – говорит о том, что поле обязательно и должно быть заполнено
  • min/max – характерно для элемента input и указывает граничные значения
  • maxlength – для input и textarea, задает максимальное количество символов
  • type – в поле типа мы можем указывать не только text, но так же такие значения как email, url, которые будут провалидированы стандартными правилами. Болле подробно о стандартах тут.
  • pattern – позволяет задать регулярное выражение для проверки поля.

API для проверки данных:

  • checkValidity() – метод, который есть, как у всей формы, так и у каждого элемента. Возвращает true/false.
  • willValidate – свойство, false, когда элемент содержит невалидные данные
  • validity – свойство, содержит объект экземпляр ValidityState
  • validationMessage – содержит сообщение ошибки
  • setCustomValidity(message) – позволяет задать сообщение ошибки

Поиграться с атрибутами проверки и API можно тут.

AngularJS расширения для валидации

Ангуляр автоматически применяется к элементу формы, в текущий $scope будет добавлена ссылка на formCotroller, ключом будет являться значение атрибута name. В нем мы можем найти ссылки на ngModelController для всех элементов формы ( так же по  name). То есть, мы добавили в наше Ангуляр приложение форму:

[html]
<form name=login>
<input name="username">
<input name="password">
</form>
[/html]

После чего в нашем скоуп будут:

[javascript]
$scope.login // formController
$scope.login.username //ngModelController
$scope.login.password //ngModelController
[/javascript]

И при этом каждый объект formController и ngModelController содержит следующие свойства (касательно валидации):

  • $valid – true/false – валидности введенных данных относительно заданных правил
  • $invalid – false/true – обратный к предыдущему
  • $pristine – true, если форма/элемент еще не использовались
  • $dirty – обратные к предыдущему
  • $touched – на элементе произошло событие blur
  • $error – объект ошибки
  • $isEmpty(value) – вспомогательный метод, который проверяет является ли значение undefined, ”, null или NaN
  • $validate() – true/false – запускает все валидаторы зарегистрированные на модели
  • $setValidity(validationErrorKey, isValid) – задание значения валидности для одного из критериев
  • $setPristine()/$setDirty() – сеттеры для $pristine/$dirty
  • $setUntouched()/$setTouched() – сеттеры для $touched

Так же ngModelController содержит свойство $validators, в котором мы можем определять наши методы для проверки данных, то есть:

[javascript]
ngModel.$validators.validCharacters = function(modelValue, viewValue) {
var value = modelValue || viewValue;
return /[0-9]+/.test(value) &&
/[a-z]+/.test(value) &&
/[A-Z]+/.test(value) &&
/\W+/.test(value);
};
[/javascript]

Вот тут можно посмотреть как изменяются свойства в зависимости от изменения значений полей:

Ангуляр полностью дублирует атрибуты валидации HTML5, причем в некоторых случаях оставляя их без изменения (как required), а в некоторых изменяет названия атрибута (ng-maxlength); плюс использует свои дополнительные:

  • required – просто парсит HTML5 атрибут
  • ng-minlength/ng-maxlength – ограничения по количеству символово (как и maxlength)
  • type – использует атрибут HTML5
  • ng-pattern – отличие в том что мы можем подставлять паттерн динамически и ошибка по умолчанию не тригерится наверх (сравнить)

!Внимание:

  • все валидируемые елементы должны содержать директиву ng-model, именно благодаря ней мы имеем доступ к ngModelController
  • если у вас в scope свойство, которая не соответствует критериям, – оно выведено не будет.

Свои кастомные проверки данных на AngularJS

Для дополнительных проверок можно создать директиву, которая будет навешиваться на элемент.

C директивой все понятно. Но вот то, как мы вернем потом значение, не так однозначно, особенно в случае асинхронных операций.

Вот пример проверки уникальности записи от ng-newsletter, атрибут-директива для элемента формы, которая следит за изменением значения:

[javascript]
app.directive(‘ensureUnique’, [‘$http’, function($http) {
return {
require: ‘ngModel’,
link: function(scope, ele, attrs, c) {
scope.$watch(attrs.ngModel, function() {
$http({
method: ‘POST’,
url: ‘/api/check/’ + attrs.ensureUnique,
data: {‘field’: attrs.ensureUnique}
}).success(function(data, status, headers, cfg) {
c.$setValidity(‘unique’, data.isUnique);
}).error(function(data, status, headers, cfg) {
c.$setValidity(‘unique’, false);
});
});
}
}
}]);
[/javascript]

А вот так (взято из оф доки) мы можем переопределить стандартную проверку:

[javascript]
app.directive(‘overwriteEmail’, function() {
var EMAIL_REGEXP = /^[a-z0-9!#$%&’*+/=?^_`{|}~.-]+@example\.com$/i;

return {
require: ‘ngModel’,
restrict: ”,
link: function(scope, elm, attrs, ctrl) {
if (ctrl && ctrl.$validators.email) {

ctrl.$validators.email = function(modelValue) {
return ctrl.$isEmpty(modelValue) || EMAIL_REGEXP.test(modelValue);
};
}
}
};
});
[/javascript]

И есть еще один интересный пример с хабра и использованием свойств котроллера модели $parsers и $formatters:

[javascript]
mod.directive(‘strongPassRequired’, function () {
var isValid = function(s) {
return s && s.length > 5 && /\D/.test(s) && /\d/.test(s);
};

return {
require:’ngModel’,
link:function (scope, elm, attrs, ngModelCtrl) {

ngModelCtrl.$parsers.unshift(function (viewValue) {
ngModelCtrl.$setValidity(‘strongPass’, isValid(viewValue));
return viewValue;
});

ngModelCtrl.$formatters.unshift(function (modelValue) {
ngModelCtrl.$setValidity(‘strongPass’, isValid(modelValue));
return modelValue;
});
}
};
});
[/javascript]

хотя думаю можно было ограничится просто использованием свойства $validators.

 

Вывод сообщений об ошибках и ng-messages

Начиная с Angular 1.3 появилась возможность подключить прекрасный модуль ngMessages, который серьезно упрощает работу по выводу ошибок. В старых версиях нам приходилось выдумывать что-то с ng-if/ng-show, а теперь все можно сделать так:

[html]
<form name="demoForm">
<input name="amount" type="number" ng-model="amount" max="100">
<div ng-messages="demoForm.amount.$error">
<div ng-message="number">Should be a number</div>
<div ng-message="max">The number is too large.</div>
</div>
</form>
[/html]

Поиграться с кодом можно тут.

!Внимание: при этом не забудьте подключить в зависимости ваш модуль:

[javascript]
angular.module(‘app’, [‘ngMessages’]);
[/javascript]

ну и конечно подключить .js файл.

Стилизация валидируемых полей

Для каждого состояния Ангуляр добавляет специальный класс:

  • ng-valid/ng-invalid
  • ng-valid-[key]ng-invalid-[key] – отдельный для каждого ключа заданного через  $setValidity
  • ng-pristine/ng-dirty
  • ng-touched/ng-untouched
  • ng-pending – ждет асинронной валидации от $asyncValidators

 

Сейчас вот задался вопросом: “Почему до сих пор нет никакого готового (и популярного) модуля для валидации данных?” Чтобы можно было на всю форму (например) навесить директиву и передать ей конфиг заполняемых полей. Если что-то знаете в тему – подскажите пожалуйста в комментариях. Иначе прийдется делать свой велосипед.