Validation is one of the
automagical capabilities of
AngularJS . Although the magic here, of course, nothing. Just such standard html tags like
form ,
input ,
select ,
textarea are also
directives . And when they are combined with
ngModel , required, ngPattern, etc., validation begins to work.
ngModel
A directive without which validation will not work. This directive is responsible for:
- two-way binding between model and view (necessary for
input
/ textarea
, select
controls / directives, etc.); - provides an interface for validation:
$render()
, $setValidity()
, $setViewValue()
, $parsers
, $formatters
, etc. (necessary for directives / validators required
, number
, email
, url
, ngPattern
, etc.); - saving the state of the control (valid / invalid, dirty / pristine, validation errors);
- setting appropriate classes for elements (ng-valid, ng-invalid, ng-dirty, ng-pristine);
- registering the control for the parent form.
Virtually all of these responsibilities are realized through
NgModelController .
The simplest example of email validation (
demo ).
')
<input type="email" ng-model="myEmail">
Forms
The state of a form depends on all controls registered for it and nested forms. The form will be valid if all controls and forms included in it are valid. You can create form hierarchies using the
ngForm directive to more conveniently process the state of individual parts of one large form. If a name is specified for a form using the name attribute, the controller of this form will be published in the appropriate scope with that name. Examples of using the properties of a form and its named controls (
demo ):
myForm.myInput.$valid
myForm.myInput.$error
myForm.$invalid
myForm.$error.required
Information about validation and errors is also reflected in the classes of forms and controls. For example:
ng-invalid-required
,
ng-dirty
,
ng-valid
,
ng-valid-email
.
Creating a validator directive
When creating a validator directive, you need to implement handlers for two cases:
- model change - all functions from the $ formatters array will be called;
- view change - all functions from the $ parsers array will be called from the pipeline . When implementing custom directives with validation support, calling these functions will result in calling the $ setViewValue method.
An example of a validator that verifies password settings is at least 6 characters, at least 1 digit, and at least one non-digital character (
demo ):
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; }); } }; });
In the
$setValidity
method, the first parameter is the string that will be used:
- as a property of the
$error
object for the control to which this validator will be applied. For example, myPassForm.myPass.$error.strongPass
; - as the css class (
ng-valid-strong-pass
or ng-invalid-strong-pass
) for the control to which this validator will be applied, and all its parent forms.
Visual directive with support for validators
For example, we implement our checkbox with an icon from Font Awesome, for which you can use, for example, the
required
validator (the
demo is for the second checkbox, and the
required
validator is used).
Source directive:
.directive('uiCheckbox', function () { return { restrict: 'EA', replace: true, transclude: true, template: '<div class="checkbox-control" ng-click="toggle()">' + '<span ng-class="{\'icon-check-empty\': !value, \'icon-check\': value}"></span>' + '<span class="checkbox-label" ng-transclude></span>' + '</div>', require: 'ngModel', scope: true, link: function (scope, element, attrs, ngModelCtrl) { scope.value = false; ngModelCtrl.$render = function () { scope.value = ngModelCtrl.$viewValue; }; scope.toggle = function () { scope.value = !scope.value; ngModelCtrl.$setViewValue(scope.value); }; } }; });
The key points here are:
ngModelController
to be passed to the link
function - require: 'ngModel'
;- implementation of the
ngModelCtrl.$render
method ngModelCtrl.$render
- called when the model specified in ngModel
; - when user actions that should lead to a change in the model, you must call
ngModelCtrl.$setViewValue
. In this case, all functions from the $ parsers array will be called.
Validation and directives with isolated scope
Suppose you have a block with several interface elements, messages on the results of validation, etc. And you need to use it on several pages. For example, a block for setting a password with confirmation, checks for the quality of a password, identity, etc. and you want to use this block again in the user registration and profile editing pages. For such a case, it is quite possible to create a directive with an isolated scope. And the validation results will be perfectly transmitted for parent forms, since parent form lookup is based on the DOM hierarchy, not the scope hierarchy.
Insulated form
Suppose there is such a task: there is a form for creating some object. To create it, you must fill in the name and do not necessarily fill out the description and list of sites with links. The Create Object button will be inactive while the creation form is invalid. At the same time, to add a new site to the list, it is necessary to fill in the Name and Url fields. And until they are filled in, the button must be inactive. Thus, to fill the list of sites will use its own nested form. But the state of its validity should not affect the parent form.
For this task, I have a workable, but not very beautiful solution. Perhaps it is useful to someone. Or someone will offer a better solution.
Demo .
Source directive code:
mod.directive('isolatedForm', function(){ return { require: 'form', link: function(scope, formElement, attrs, formController) { var parentFormCtrl = formElement.parent().controller('form'); var core$setValidity = formController.$setValidity; formController.$setValidity = function(validationToken, isValid, control) { core$setValidity(validationToken, isValid, control); if (!isValid && parentFormCtrl) { parentFormCtrl.$setValidity(validationToken, true, formController); } } } }; });
The basic idea is that every time a form wants to inform upstairs that something is invalid in it, instead report it is valid.