docs(aio): tech edits to form validation

closes #18495
This commit is contained in:
Kapunahele Wong 2017-08-02 18:01:09 -04:00 committed by Victor Berchet
parent 939dc44391
commit 9b015a95eb
3 changed files with 51 additions and 104 deletions

View File

@ -24,7 +24,7 @@ export class HeroFormReactiveComponent implements OnInit {
'name': new FormControl(this.hero.name, [ 'name': new FormControl(this.hero.name, [
Validators.required, Validators.required,
Validators.minLength(4), Validators.minLength(4),
forbiddenNameValidator(/bob/i) forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator.
]), ]),
'alterEgo': new FormControl(this.hero.alterEgo), 'alterEgo': new FormControl(this.hero.alterEgo),
'power': new FormControl(this.hero.power, Validators.required) 'power': new FormControl(this.hero.power, Validators.required)

View File

@ -19,23 +19,12 @@ export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
providers: [{provide: NG_VALIDATORS, useExisting: ForbiddenValidatorDirective, multi: true}] providers: [{provide: NG_VALIDATORS, useExisting: ForbiddenValidatorDirective, multi: true}]
// #enddocregion directive-providers // #enddocregion directive-providers
}) })
export class ForbiddenValidatorDirective implements Validator, OnChanges { export class ForbiddenValidatorDirective implements Validator {
@Input() forbiddenName: string; @Input() forbiddenName: string;
private valFn = Validators.nullValidator;
ngOnChanges(changes: SimpleChanges): void {
const change = changes['forbiddenName'];
if (change) {
const val: string | RegExp = change.currentValue;
const re = val instanceof RegExp ? val : new RegExp(val, 'i');
this.valFn = forbiddenNameValidator(re);
} else {
this.valFn = Validators.nullValidator;
}
}
validate(control: AbstractControl): {[key: string]: any} { validate(control: AbstractControl): {[key: string]: any} {
return this.valFn(control); return this.forbiddenName ? forbiddenNameValidator(new RegExp(this.forbiddenName, 'i'))(control)
: null;
} }
} }
// #enddocregion directive // #enddocregion directive

View File

@ -17,27 +17,17 @@ If you're new to forms, start by reviewing the [Forms](guide/forms) and
</div> </div>
{@a live-example}
**Try the live example to see and download the full cookbook source code.**
<live-example name="form-validation" embedded=true img="guide/form-validation/plunker.png">
</live-example>
## Template-driven validation ## Template-driven validation
To add validation to a template-driven form, you add the same validation attributes as you To add validation to a template-driven form, you add the same validation attributes as you
would with [native HTML form validation](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation). would with [native HTML form validation](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation).
Angular uses directives to match these attributes with validator functions in the framework. Angular uses directives to match these attributes with validator functions in the framework.
Every time the value of a form control changes, Angular will run validation and generate Every time the value of a form control changes, Angular runs validation and generates
either a list of validation errors (which results in an INVALID status) or null (which either a list of validation errors, which results in an INVALID status, or null, which results in a VALID status.
results in a VALID status).
You can then inspect the control's state by exporting `ngModel` to a local template variable. You can then inspect the control's state by exporting `ngModel` to a local template variable.
In the example below, it's exported into a variable called `name`: The following example exports `NgModel` into a variable called `name`:
<code-example path="form-validation/src/app/template/hero-form-template.component.html" region="name-with-error-msg" title="template/hero-form-template.component.html (name)" linenums="false"> <code-example path="form-validation/src/app/template/hero-form-template.component.html" region="name-with-error-msg" title="template/hero-form-template.component.html (name)" linenums="false">
@ -47,12 +37,11 @@ In the example below, it's exported into a variable called `name`:
Note the following: Note the following:
* The `<input>` element carries the HTML validation attributes: `required` and `minlength`. It * The `<input>` element carries the HTML validation attributes: `required` and `minlength`. It
also carries a custom validator directive, `forbiddenName` (more on that [later](guide/form-validation#custom-validator)). also carries a custom validator directive, `forbiddenName`. For more
information, see [Custom validators](guide/form-validation#custom-validators) section.
* The `NgModel` directive has been exported into a local template variable called `name` * `#name="ngModel"` exports `NgModel` into a local variable callled `name`. `NgModel` mirrors many of the properties of its underlying
by setting `#name="ngModel"`. `NgModel` mirrors many of the properties of its underlying `FormControl` instance, so you can use this in the template to check for control states such as `valid` and `dirty`. For a full list of control properties, see the [AbstractControl](api/forms/AbstractControl)
`FormControl` instance, so you can use this in the template to check for control states
such as `valid` and `dirty`. For a full list of control properties, see the [AbstractControl](api/forms/AbstractControl)
API reference. API reference.
* The `*ngIf` on the `<div>` element reveals a set of nested message `divs` * The `*ngIf` on the `<div>` element reveals a set of nested message `divs`
@ -62,9 +51,6 @@ but only if the `name` is invalid and the control is either `dirty` or `touched`
There are messages for `required`, `minlength`, and `forbiddenName`. There are messages for `required`, `minlength`, and `forbiddenName`.
{@a why-check}
<div class="l-sub-section"> <div class="l-sub-section">
@ -72,53 +58,37 @@ There are messages for `required`, `minlength`, and `forbiddenName`.
#### Why check _dirty_ and _touched_? #### Why check _dirty_ and _touched_?
You may not want your application to display errors before the user has a chance to edit the form. You may not want your application to display errors before the user has a chance to edit the form.
The checks for `dirty` and `touched` prevent errors from showing until the user changes the value The checks for `dirty` and `touched` prevent errors from showing until the user
(turning the control dirty) or blurs the form control element (setting the control to touched). does one of two things: changes the value,
turning the control dirty; or blurs the form control element, setting the control to touched.
</div> </div>
<div class="l-sub-section">
Check out the [live example](guide/form-validation#live-example) for the full template-driven component.
</div>
{@a reactive}
## Reactive form validation ## Reactive form validation
In a reactive form, the source of truth is the component class. Instead of adding validators In a reactive form, the source of truth is the component class. Instead of adding validators through attributes in the template, you add validator functions directly to the form control model in the component class. Angular then calls these functions whenever the value of the control changes.
through attributes in the template, you add validator functions directly to the form control
model in the component class. These functions are then called whenever the value of the
control changes.
### Validator functions ### Validator functions
There are two types of validator functions: sync validators and async validators. There are two types of validator functions: sync validators and async validators.
* **Sync validators**: functions that take a control instance and immediately return either a * **Sync validators**: functions that take a control instance and immediately return either a set of validation errors or `null`. You can pass these in as the second argument when you instantiate a `FormControl`.
a set of validation errors or `null`. These can be passed in as the second argument when
you instantiate a `FormControl`.
* **Async validators**: functions that take a control instance and return a Promise * **Async validators**: functions that take a control instance and return a Promise
or Observable that later emits a set of validation errors or `null`. These can be passed or Observable that later emits a set of validation errors or `null`. You can
in as the third argument when you instantiate a `FormControl`. pass these in as the third argument when you instantiate a `FormControl`.
Note: for performance reasons, async validators are only run if all sync validators pass. Each Note: for performance reasons, Angular only runs async validators if all sync validators pass. Each must complete before errors are set.
must complete before errors are set.
### Built-in validators ### Built-in validators
You can choose to [write your own validator functions](guide/form-validation#custom-validator), or you can use some of our built-in You can choose to [write your own validator functions](guide/form-validation#custom-validators), or you can use some of
validators. Angular's built-in validators.
The same built-in validators that were available as attributes in template-driven forms - The same built-in validators that are available as attributes in template-driven forms, such as `required` and `minlength`, are all available to use as functions from the `Validators` class. For a full list of built-in validators, see the [Validators](api/forms/Validators) API reference.
`required`, `minlength`, etc - are all available to use as functions from the
`Validators` class. For a full list of built-in validators, see the [Validators](api/forms/Validators)
API reference.
If our hero form is updated to be a reactive form, we'll want to use some of the same To update the hero form to be a reactive form, you can use some of the same
built-in validators (this time, in function form). See below: built-in validators&mdash;this time, in function form. See below:
{@a reactive-component-class} {@a reactive-component-class}
@ -127,15 +97,12 @@ built-in validators (this time, in function form). See below:
Note that: Note that:
* The name control sets up two built-in validators - `Validators.required` and `Validators.minLength(4)` - and * The name control sets up two built-in validators&mdash;`Validators.required` and `Validators.minLength(4)`&mdash;and one custom validator, `forbiddenNameValidator`. For more details see the [Custom validators](guide/form-validation#custom-validators) section in this guide.
one custom validator, `forbiddenNameValidator` (more on that [later](guide/form-validation#custom-validator)). * As these validators are all sync validators, you pass them in as the second argument.
* As these validators are all sync validators, they are passed in as the second argument. * Support multiple validators by passing the functions in as an array.
* Multiple validators are supported by passing the functions in as an array. * This example adds a few getter methods. In a reactive form, you can always access any form control through the `get` method on its parent group, but sometimes it's useful to define getters as shorthands
* A few getter methods have been added. In a reactive form, you can always access any form control
through the `get` method on its parent group, but sometimes it's useful to define getters as shorthands
for the template. for the template.
{@a reactive-component-template}
If you look at the template for the name input again, it is fairly similar to the template-driven example. If you look at the template for the name input again, it is fairly similar to the template-driven example.
@ -149,28 +116,19 @@ Key takeaways:
* The `required` attribute is still present. While it's not necessary for validation purposes, * The `required` attribute is still present. While it's not necessary for validation purposes,
you may want to keep it in your template for CSS styling or accessibility reasons. you may want to keep it in your template for CSS styling or accessibility reasons.
<div class="l-sub-section">
Run the [live example](guide/form-validation#live-example) to see the full reactive form component.
</div>
{@a custom-validator}
## Custom validators ## Custom validators
The built-in validators won't always match the exact use case of your application. Sometimes you'll want to Since the built-in validators won't always match the exact use case of your application, sometimes you'll want to create a custom validator.
create a custom validator.
You may remember the `forbiddenNameValidator` function from the examples above. Here's what the definition of Consider the `forbiddenNameValidator` function from previous
that function looks like: [examples](guide/form-validation#reactive-component-class) in
this guide. Here's what the definition of that function looks like:
<code-example path="form-validation/src/app/shared/forbidden-name.directive.ts" region="custom-validator" title="shared/forbidden-name.directive.ts (forbiddenNameValidator)" linenums="false"> <code-example path="form-validation/src/app/shared/forbidden-name.directive.ts" region="custom-validator" title="shared/forbidden-name.directive.ts (forbiddenNameValidator)" linenums="false">
</code-example> </code-example>
The function is actually a factory that takes a regular expression to detect a _specific_ forbidden name The function is actually a factory that takes a regular expression to detect a _specific_ forbidden name and returns a validator function.
and returns a validator function.
In this sample, the forbidden name is "bob", so the validator will reject any hero name containing "bob". In this sample, the forbidden name is "bob", so the validator will reject any hero name containing "bob".
Elsewhere it could reject "alice" or any name that the configuring regular expression matches. Elsewhere it could reject "alice" or any name that the configuring regular expression matches.
@ -179,7 +137,7 @@ The `forbiddenNameValidator` factory returns the configured validator function.
That function takes an Angular control object and returns _either_ That function takes an Angular control object and returns _either_
null if the control value is valid _or_ a validation error object. null if the control value is valid _or_ a validation error object.
The validation error object typically has a property whose name is the validation key, `'forbiddenName'`, The validation error object typically has a property whose name is the validation key, `'forbiddenName'`,
and whose value is an arbitrary dictionary of values that you could insert into an error message (`{name}`). and whose value is an arbitrary dictionary of values that you could insert into an error message, `{name}`.
### Adding to reactive forms ### Adding to reactive forms
@ -192,9 +150,9 @@ to the `FormControl`.
### Adding to template-driven forms ### Adding to template-driven forms
In template-driven forms, you don't have direct access to the `FormControl` instance, so you can't pass the In template-driven forms, you don't have direct access to the `FormControl` instance, so you can't pass the
validator in that way. Instead, you'll want to add a directive to the template. validator in like you can for reactive forms. Instead, you need to add a directive to the template.
The corresponding `ForbiddenValidatorDirective` is a wrapper around the `forbiddenNameValidator`. The corresponding `ForbiddenValidatorDirective` serves as a wrapper around the `forbiddenNameValidator`.
Angular recognizes the directive's role in the validation process because the directive registers itself Angular recognizes the directive's role in the validation process because the directive registers itself
with the `NG_VALIDATORS` provider, a provider with an extensible collection of validators. with the `NG_VALIDATORS` provider, a provider with an extensible collection of validators.
@ -209,8 +167,7 @@ comes together:
<code-example path="form-validation/src/app/shared/forbidden-name.directive.ts" region="directive" title="shared/forbidden-name.directive.ts (directive)"> <code-example path="form-validation/src/app/shared/forbidden-name.directive.ts" region="directive" title="shared/forbidden-name.directive.ts (directive)">
</code-example> </code-example>
Once the `ForbiddenValidatorDirective` is ready, you can simply add its selector (`forbiddenName`) Once the `ForbiddenValidatorDirective` is ready, you can simply add its selector, `forbiddenName`, to any input element to activate it. For example:
to any input element to activate it. For example:
<code-example path="form-validation/src/app/template/hero-form-template.component.html" region="name-input" title="template/hero-form-template.component.html (forbidden-name-input)" linenums="false"> <code-example path="form-validation/src/app/template/hero-form-template.component.html" region="name-input" title="template/hero-form-template.component.html (forbidden-name-input)" linenums="false">
@ -228,23 +185,24 @@ doesnt have a `forbiddenName`.
</div> </div>
## Control status classes ## Control status CSS classes
Like in AngularJS, many control properties are automatically mirrored onto the form control Like in AngularJS, Angular automatically mirrors many control properties onto the form control element as CSS classes. You can use these classes to style form control elements according to the state of the form. The following classes are currently supported:
element as classes. These classes can be used to style form control elements according to
the state of the form. The following classes are currently supported:
* ng-valid * `.ng-valid`
* ng-invalid * `.ng-invalid`
* ng-pending * `.ng-pending`
* ng-pristine * `.ng-pristine`
* ng-dirty * `.ng-dirty`
* ng-untouched * `.ng-untouched`
* ng-touched * `.ng-touched`
In the hero form, the `.ng-valid` and `.ng-invalid` classes are used to set the color of The hero form uses the `.ng-valid` and `.ng-invalid` classes to
each form control's border. set the color of each form control's border.
<code-example path="form-validation/src/forms.css" title="forms.css (status classes)"> <code-example path="form-validation/src/forms.css" title="forms.css (status classes)">
</code-example> </code-example>
**You can run the <live-example></live-example> to see the complete reactive and template-driven example code.**