Skip to content

Commit a52ccc8

Browse files
committed
docs(guide/forms): update custom validation section with $validators
1 parent 89a8bf7 commit a52ccc8

File tree

1 file changed

+47
-44
lines changed

1 file changed

+47
-44
lines changed

docs/content/guide/forms.ngdoc

+47-44
Original file line numberDiff line numberDiff line change
@@ -267,35 +267,21 @@ This example shows how to debounce model changes. Model will be updated only 250
267267
</file>
268268
</example>
269269

270-
271-
272270
# Custom Validation
273271

274272
Angular provides basic implementation for most common html5 {@link ng.directive:input input}
275273
types: ({@link input[text] text}, {@link input[number] number}, {@link input[url] url}, {@link input[email] email}, {@link input[radio] radio}, {@link input[checkbox] checkbox}), as well as some directives for validation (`required`, `pattern`, `minlength`, `maxlength`, `min`, `max`).
276274

277-
Defining your own validator can be done by defining your own directive which adds a custom validation function to the `ngModel` {@link ngModel.NgModelController controller}.
278-
To get a hold of the controller the directive specifies a dependency as shown in the example below.
279-
The validation can occur in two places:
280-
281-
* **Model to View update** -
282-
Whenever the bound model changes, all functions in {@link ngModel.NgModelController#$formatters NgModelController#$formatters} array are pipe-lined, so that each of these functions has an opportunity to format the value and change validity state of the form control through {@link ngModel.NgModelController#$setValidity NgModelController#$setValidity}.
275+
With a custom directive, you can add your own validation functions to the `$validators` object on the {@link ngModel.NgModelController `ngModelController`}. To get a hold of the controller, you require it in the directive as shown in the example below.
283276

284-
* **View to Model update** -
285-
In a similar way, whenever a user interacts with a control it calls {@link ngModel.NgModelController#$setViewValue NgModelController#$setViewValue}.
286-
This in turn pipelines all functions in the {@link ngModel.NgModelController#$parsers NgModelController#$parsers} array, so that each of these functions has an opportunity to convert the value and change validity state of the form control through {@link ngModel.NgModelController#$setValidity NgModelController#$setValidity}.
277+
Each function in the `$validators` object receives the `modelValue` and the `viewValue`as parameters. Angular will then call `$setValidity` internally with the function's return value (`true`: valid, `false`: invalid). The validation functions are executed every time an input is changed (`$setViewValue` is called) or whenever the bound `model` changes. Validation happens after successfully running $parsers and $formatters, respectively. Failed validators are stored by key in `ngModelController.$error`.
287278

288-
In the following example we create two directives.
279+
Additionally, there is the `$asyncValidators` object which handles asynchronous validation, such as making an `$http` request to the backend. Functions added to the object must return a promise that must be `resolved` when valid or rejected when `invalid`. In-progress async validations are added to `ngModelController.$pending`.
289280

290-
* The first one is `integer` and it validates whether the input is a valid integer.
291-
For example `1.23` is an invalid value, since it contains a fraction.
292-
Note that we unshift the array instead of pushing.
293-
This is because we want it to be the first parser and consume the control string value, as we need to execute the validation function before a conversion to number occurs.
294-
295-
* The second directive is a `smart-float`.
296-
It parses both `1.2` and `1,2` into a valid float number `1.2`.
297-
Note that we can't use input type `number` here as HTML5 browsers would not allow the user to type what it would consider an invalid number such as `1,2`.
281+
In the following example we create two directives:
282+
* An `integer` directive that validates whether the input is a valid integer. For example `1.23` is an invalid value, since it contains a fraction. Note that we validate the viewValue (the string value of the control), and not the modelValue. This is because input[number] converts the viewValue to a number when running the `$parsers`.
298283

284+
* A `username` directive that asynchronously checks if a user-entered value is already taken. We mock the server request with a `$q` deferred.
299285

300286
<example module="form-example1">
301287
<file name="index.html">
@@ -304,18 +290,18 @@ In the following example we create two directives.
304290
Size (integer 0 - 10):
305291
<input type="number" ng-model="size" name="size"
306292
min="0" max="10" integer />{{size}}<br />
307-
<span ng-show="form.size.$error.integer">This is not valid integer!</span>
293+
<span ng-show="form.size.$error.integer">The value is not a valid integer!</span>
308294
<span ng-show="form.size.$error.min || form.size.$error.max">
309295
The value must be in range 0 to 10!</span>
310296
</div>
311297

312298
<div>
313-
Length (float):
314-
<input type="text" ng-model="length" name="length" smart-float />
315-
{{length}}<br />
316-
<span ng-show="form.length.$error.float">
317-
This is not a valid float number!</span>
299+
Username:
300+
<input type="text" ng-model="name" name="name" username />{{name}}<br />
301+
<span ng-show="form.name.$pending.username">Checking if this name is available ...</span>
302+
<span ng-show="form.name.$error.username">This username is already taken!</span>
318303
</div>
304+
319305
</form>
320306
</file>
321307

@@ -327,35 +313,52 @@ In the following example we create two directives.
327313
return {
328314
require: 'ngModel',
329315
link: function(scope, elm, attrs, ctrl) {
330-
ctrl.$parsers.unshift(function(viewValue) {
316+
ctrl.$validators.integer = function(modelValue, viewValue) {
317+
if (ctrl.$isEmpty(modelValue)) {
318+
// consider empty models to be valid
319+
return true;
320+
}
321+
331322
if (INTEGER_REGEXP.test(viewValue)) {
332323
// it is valid
333-
ctrl.$setValidity('integer', true);
334-
return viewValue;
335-
} else {
336-
// it is invalid, return undefined (no model update)
337-
ctrl.$setValidity('integer', false);
338-
return undefined;
324+
return true;
339325
}
340-
});
326+
327+
// it is invalid
328+
return false;
329+
};
341330
}
342331
};
343332
});
344333

345-
var FLOAT_REGEXP = /^\-?\d+((\.|\,)\d+)?$/;
346-
app.directive('smartFloat', function() {
334+
app.directive('username', function($q, $timeout) {
347335
return {
348336
require: 'ngModel',
349337
link: function(scope, elm, attrs, ctrl) {
350-
ctrl.$parsers.unshift(function(viewValue) {
351-
if (FLOAT_REGEXP.test(viewValue)) {
352-
ctrl.$setValidity('float', true);
353-
return parseFloat(viewValue.replace(',', '.'));
354-
} else {
355-
ctrl.$setValidity('float', false);
356-
return undefined;
338+
var usernames = ['Jim', 'John', 'Jill', 'Jackie'];
339+
340+
ctrl.$asyncValidators.username = function(modelValue, viewValue) {
341+
342+
if (ctrl.$isEmpty(modelValue)) {
343+
// consider empty model valid
344+
return $q.when();
357345
}
358-
});
346+
347+
var def = $q.defer();
348+
349+
$timeout(function() {
350+
// Mock a delayed response
351+
if (usernames.indexOf(modelValue) == -1) {
352+
// The username is available
353+
def.resolve();
354+
} else {
355+
def.reject();
356+
}
357+
358+
}, 2000);
359+
360+
return def.promise;
361+
};
359362
}
360363
};
361364
});

0 commit comments

Comments
 (0)