Skip to content

Commit d5457bb

Browse files
committed
docs(guide/forms): update custom validation section with $validators
1 parent 036871d commit d5457bb

File tree

1 file changed

+73
-55
lines changed

1 file changed

+73
-55
lines changed

docs/content/guide/forms.ngdoc

+73-55
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ This allows us to extend the above example with these features:
190190

191191

192192

193-
# Custom triggers
193+
# Custom model update triggers
194194

195195
By default, any change to the content will trigger a model update and form validation. You can
196196
override this behavior using the {@link ng.directive:ngModelOptions ngModelOptions} directive to
@@ -267,39 +267,40 @@ 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}
275-
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`).
276-
277-
You can define your own validator by defining a directive which adds a validation function to the `ngModel` {@link ngModel.NgModelController controller}.
278-
The directive can get ahold of `ngModel` by specifying `require: 'ngModel'` in the directive definition.
279-
See below for an example.
280-
281-
Validation runs in two places:
282-
283-
* **Model to View update** -
284-
The functions in {@link ngModel.NgModelController#$formatters `NgModelController.$formatters`} are pipelined.
285-
Whenever the bound model changes, 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`}.
286-
287-
* **View to Model update** -
288-
In a similar way, whenever a user interacts with a control it calls {@link ngModel.NgModelController#$setViewValue `NgModelController.$setViewValue`}.
289-
290-
This in turn runs all functions in the {@link ngModel.NgModelController#$parsers `NgModelController.$parsers`} array as a pipeline. Each function in `$parsers` has an opportunity to convert the value and change validity state of the form control through {@link ngModel.NgModelController#$setValidity `NgModelController.$setValidity`}.
291-
292-
In the following example we create two directives.
293-
294-
* The first one is `integer` and it validates whether the input is a valid integer.
295-
For example `1.23` is an invalid value, since it contains a fraction.
296-
Note that we unshift the array instead of pushing.
297-
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.
298-
299-
* The second directive is a `smart-float`.
300-
It parses both `1.2` and `1,2` into a valid float number `1.2`.
301-
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`.
302-
273+
types: ({@link input[text] text}, {@link input[number] number}, {@link input[url] url},
274+
{@link input[email] email}, {@link input[radio] radio}, {@link input[checkbox] checkbox}),
275+
as well as some directives for validation (`required`, `pattern`, `minlength`, `maxlength`,
276+
`min`, `max`).
277+
278+
With a custom directive, you can add your own validation functions to the `$validators` object on
279+
the {@link ngModel.NgModelController `ngModelController`}. To get a hold of the controller,
280+
you require it in the directive as shown in the example below.
281+
282+
Each function in the `$validators` object receives the `modelValue` and the `viewValue`
283+
as parameters. Angular will then call `$setValidity` internally with the function's return value
284+
(`true`: valid, `false`: invalid). The validation functions are executed every time an input
285+
is changed (`$setViewValue` is called) or whenever the bound `model` changes.
286+
Validation happens after successfully running `$parsers` and `$formatters`, respectively.
287+
Failed validators are stored by key in
288+
{@link ngModel.NgModelController#$error `ngModelController.$error`}.
289+
290+
Additionally, there is the `$asyncValidators` object which handles asynchronous validation,
291+
such as making an `$http` request to the backend. Functions added to the object must return
292+
a promise that must be `resolved` when valid or `rejected` when invalid.
293+
In-progress async validations are stored by key in
294+
{@link ngModel.NgModelController#$pending `ngModelController.$pending`}.
295+
296+
In the following example we create two directives:
297+
* An `integer` directive that validates whether the input is a valid integer. For example,
298+
`1.23` is an invalid value, since it contains a fraction. Note that we validate the viewValue
299+
(the string value of the control), and not the modelValue. This is because input[number] converts
300+
the viewValue to a number when running the `$parsers`.
301+
302+
* A `username` directive that asynchronously checks if a user-entered value is already taken.
303+
We mock the server request with a `$q` deferred.
303304

304305
<example module="form-example1">
305306
<file name="index.html">
@@ -308,18 +309,18 @@ In the following example we create two directives.
308309
Size (integer 0 - 10):
309310
<input type="number" ng-model="size" name="size"
310311
min="0" max="10" integer />{{size}}<br />
311-
<span ng-show="form.size.$error.integer">This is not valid integer!</span>
312+
<span ng-show="form.size.$error.integer">The value is not a valid integer!</span>
312313
<span ng-show="form.size.$error.min || form.size.$error.max">
313314
The value must be in range 0 to 10!</span>
314315
</div>
315316

316317
<div>
317-
Length (float):
318-
<input type="text" ng-model="length" name="length" smart-float />
319-
{{length}}<br />
320-
<span ng-show="form.length.$error.float">
321-
This is not a valid float number!</span>
318+
Username:
319+
<input type="text" ng-model="name" name="name" username />{{name}}<br />
320+
<span ng-show="form.name.$pending.username">Checking if this name is available ...</span>
321+
<span ng-show="form.name.$error.username">This username is already taken!</span>
322322
</div>
323+
323324
</form>
324325
</file>
325326

@@ -331,35 +332,52 @@ In the following example we create two directives.
331332
return {
332333
require: 'ngModel',
333334
link: function(scope, elm, attrs, ctrl) {
334-
ctrl.$parsers.unshift(function(viewValue) {
335+
ctrl.$validators.integer = function(modelValue, viewValue) {
336+
if (ctrl.$isEmpty(modelValue)) {
337+
// consider empty models to be valid
338+
return true;
339+
}
340+
335341
if (INTEGER_REGEXP.test(viewValue)) {
336342
// it is valid
337-
ctrl.$setValidity('integer', true);
338-
return viewValue;
339-
} else {
340-
// it is invalid, return undefined (no model update)
341-
ctrl.$setValidity('integer', false);
342-
return undefined;
343+
return true;
343344
}
344-
});
345+
346+
// it is invalid
347+
return false;
348+
};
345349
}
346350
};
347351
});
348352

349-
var FLOAT_REGEXP = /^\-?\d+((\.|\,)\d+)?$/;
350-
app.directive('smartFloat', function() {
353+
app.directive('username', function($q, $timeout) {
351354
return {
352355
require: 'ngModel',
353356
link: function(scope, elm, attrs, ctrl) {
354-
ctrl.$parsers.unshift(function(viewValue) {
355-
if (FLOAT_REGEXP.test(viewValue)) {
356-
ctrl.$setValidity('float', true);
357-
return parseFloat(viewValue.replace(',', '.'));
358-
} else {
359-
ctrl.$setValidity('float', false);
360-
return undefined;
357+
var usernames = ['Jim', 'John', 'Jill', 'Jackie'];
358+
359+
ctrl.$asyncValidators.username = function(modelValue, viewValue) {
360+
361+
if (ctrl.$isEmpty(modelValue)) {
362+
// consider empty model valid
363+
return $q.when();
361364
}
362-
});
365+
366+
var def = $q.defer();
367+
368+
$timeout(function() {
369+
// Mock a delayed response
370+
if (usernames.indexOf(modelValue) === -1) {
371+
// The username is available
372+
def.resolve();
373+
} else {
374+
def.reject();
375+
}
376+
377+
}, 2000);
378+
379+
return def.promise;
380+
};
363381
}
364382
};
365383
});

0 commit comments

Comments
 (0)