-
Hero Form (Template-Driven)
-
diff --git a/public/docs/_examples/cb-form-validation/ts/app/template/hero-form-template.component.ts b/public/docs/_examples/cb-form-validation/ts/app/template/hero-form-template1.component.ts
similarity index 71%
rename from public/docs/_examples/cb-form-validation/ts/app/template/hero-form-template.component.ts
rename to public/docs/_examples/cb-form-validation/ts/app/template/hero-form-template1.component.ts
index 9149ad3126..845fcc9abc 100644
--- a/public/docs/_examples/cb-form-validation/ts/app/template/hero-form-template.component.ts
+++ b/public/docs/_examples/cb-form-validation/ts/app/template/hero-form-template1.component.ts
@@ -8,15 +8,15 @@ import { Hero } from '../shared/hero';
@Component({
moduleId: module.id,
- selector: 'hero-form-template',
- templateUrl: 'hero-form-template.component.html'
+ selector: 'hero-form-template1',
+ templateUrl: 'hero-form-template1.component.html'
})
// #docregion class
-export class HeroFormTemplateComponent {
+export class HeroFormTemplate1Component {
powers = ['Really Smart', 'Super Flexible', 'Weather Changer'];
- model = new Hero(18, 'Dr. WhatIsHisWayTooLongName', this.powers[0], 'Dr. What');
+ hero = new Hero(18, 'Dr. WhatIsHisWayTooLongName', this.powers[0], 'Dr. What');
submitted = false;
@@ -24,20 +24,23 @@ export class HeroFormTemplateComponent {
this.submitted = true;
}
// #enddocregion class
-
+// #enddocregion
// Reset the form with a new hero AND restore 'pristine' class state
// by toggling 'active' flag which causes the form
// to be removed/re-added in a tick via NgIf
// TODO: Workaround until NgForm has a reset method (#6822)
active = true;
+// #docregion
// #docregion class
- newHero() {
- this.model = new Hero(42, '', '');
+ addHero() {
+ this.hero = new Hero(42, '', '');
// #enddocregion class
+// #enddocregion
this.active = false;
setTimeout(() => this.active = true, 0);
+// #docregion
// #docregion class
}
}
diff --git a/public/docs/_examples/cb-form-validation/ts/app/template/hero-form-template2.component.html b/public/docs/_examples/cb-form-validation/ts/app/template/hero-form-template2.component.html
new file mode 100644
index 0000000000..8bb7066541
--- /dev/null
+++ b/public/docs/_examples/cb-form-validation/ts/app/template/hero-form-template2.component.html
@@ -0,0 +1,52 @@
+
+
+
+
Hero Form 2 (Template & Messages)
+
+
+
+
+
+
diff --git a/public/docs/_examples/cb-form-validation/ts/app/template/hero-form-template2.component.ts b/public/docs/_examples/cb-form-validation/ts/app/template/hero-form-template2.component.ts
new file mode 100644
index 0000000000..ae6c0367b4
--- /dev/null
+++ b/public/docs/_examples/cb-form-validation/ts/app/template/hero-form-template2.component.ts
@@ -0,0 +1,99 @@
+/* tslint:disable: member-ordering forin */
+// #docplaster
+// #docregion
+import { Component, AfterViewChecked, ViewChild } from '@angular/core';
+import { NgForm } from '@angular/forms';
+
+import { Hero } from '../shared/hero';
+
+@Component({
+ moduleId: module.id,
+ selector: 'hero-form-template2',
+ templateUrl: 'hero-form-template2.component.html'
+})
+export class HeroFormTemplate2Component implements AfterViewChecked {
+
+ powers = ['Really Smart', 'Super Flexible', 'Weather Changer'];
+
+ hero = new Hero(18, 'Dr. WhatIsHisWayTooLongName', this.powers[0], 'Dr. What');
+
+ submitted = false;
+
+ onSubmit() {
+ this.submitted = true;
+ }
+// #enddocregion
+
+ // Reset the form with a new hero AND restore 'pristine' class state
+ // by toggling 'active' flag which causes the form
+ // to be removed/re-added in a tick via NgIf
+ // TODO: Workaround until NgForm has a reset method (#6822)
+ active = true;
+// #docregion
+
+ addHero() {
+ this.hero = new Hero(42, '', '');
+// #enddocregion
+
+ this.active = false;
+ setTimeout(() => this.active = true, 0);
+// #docregion
+ }
+
+ // #docregion view-child
+ heroForm: NgForm;
+ @ViewChild('heroForm') currentForm: NgForm;
+
+ ngAfterViewChecked() {
+ this.formChanged();
+ }
+
+ formChanged() {
+ if (this.currentForm === this.heroForm) { return; }
+ this.heroForm = this.currentForm;
+ if (this.heroForm) {
+ this.heroForm.valueChanges
+ .subscribe(data => this.onValueChanged(data));
+ }
+ }
+ // #enddocregion view-child
+
+ // #docregion handler
+ onValueChanged(data?: any) {
+ const controls = this.heroForm ? this.heroForm.controls : {};
+
+ for (const field in this.formErrors) {
+ // clear previous error message (if any)
+ this.formErrors[field] = '';
+ const control = controls[field];
+
+ if (control && control.dirty && !control.valid) {
+ const messages = this.validationMessages[field];
+ for (const key in control.errors) {
+ this.formErrors[field] += messages[key] + ' ';
+ }
+ }
+ }
+ }
+
+ formErrors = {
+ 'name': '',
+ 'power': ''
+ };
+ // #enddocregion handler
+
+ // #docregion messages
+ validationMessages = {
+ 'name': {
+ 'required': 'Name is required.',
+ 'minlength': 'Name must be at least 4 characters long.',
+ 'maxlength': 'Name cannot be more than 24 characters long.',
+ 'forbiddenName': 'Someone named "Bob" cannot be a hero.'
+ },
+ 'power': {
+ 'required': 'Power is required.'
+ }
+ };
+ // #enddocregion messages
+}
+// #enddocregion
diff --git a/public/docs/_examples/cb-form-validation/ts/index.html b/public/docs/_examples/cb-form-validation/ts/index.html
index 43a3a70f60..6aea5beaa4 100644
--- a/public/docs/_examples/cb-form-validation/ts/index.html
+++ b/public/docs/_examples/cb-form-validation/ts/index.html
@@ -8,7 +8,7 @@
-
+
diff --git a/public/docs/ts/latest/cookbook/form-validation.jade b/public/docs/ts/latest/cookbook/form-validation.jade
index ba0e9693de..877026adcc 100644
--- a/public/docs/ts/latest/cookbook/form-validation.jade
+++ b/public/docs/ts/latest/cookbook/form-validation.jade
@@ -1,198 +1,462 @@
include ../_util-fns
-
+a#top
:marked
- We can improve overall data quality by validating user input for accuracy and completeness.
+ We can improve overall data quality by validating user input for accuracy and completeness.
- In this cookbook we show how to validate user input in the UI and display useful validation messages
- using first the template-driven forms and then the reactive forms approach.
+ In this cookbook we show how to validate user input in the UI and display useful validation messages
+ using first the template-driven forms and then the reactive forms approach.
+.l-sub-section
+ :marked
+ Learn more about these choices in the [Forms chapter.](../guide/forms.html)
- An Angular component consists of a template and a component class containing the code that drives the template.
- The first example demonstrates input validation entirely within the template.
- The second example moves the validation logic out of the template and into the component class,
- giving the developer more control and easier unit testing.
-
- Both examples are based on the the sample form in the [Forms chapter.](../guide/forms.html)
-
-
+a#toc
:marked
## Contents
- [Template-Driven Forms Approach](#template-driven)
+ [Simple Template-Driven Forms](#template1)
- [Reactive Forms Approach](#reactive)
+ [Template-Driven Forms with validation messages in code](#template2)
- **Try the live example**
-
+ [Reactive Forms with validation in code](#reactive)
+
+ [Custom validation](#custom-validation)
+
+ [Testing](#testing)
+
+a#live-example
+:marked
+ **Try the live example to see and download the full cookbook source code**
+live-example(name="cb-form-validation" embedded img="cookbooks/form-validation/plunker.png")
.l-main-section
-
+a#template1
:marked
- ## Template-Driven Forms
+ ## Simple Template-Driven Forms
- In the template-driven approach,
- each control on the form defines its own validation and validation messages in the template.
+ In the template-driven approach, you arrange
+ [form elements](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Forms_in_HTML) in the component's template.
+ You add Angular form directives (mostly directives beginning `ng...`) to help
+ Angular construct a corresponding internal control model that implements form functionality.
+ We say that the control model is _implicit_ in the template.
+
+ To validate user input, you add [HTML validation attributes](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation)
+ to the elements. Angular interprets those as well, adding validator functions to the control model.
+
+ Angular exposes information about the state of the controls including
+ whether the user has "touched" the control or made changes and if the control values are valid.
+
+ In the first template validation example,
+ we add more HTML to read that control state and update the display appropriately.
Here's an excerpt from the template html for a single input box control bound to the hero name:
-+makeExample('cb-form-validation/ts/app/template/hero-form-template.component.html','name-with-error-msg','app/template/hero-form-template.component.html (Hero name)')
++makeExample('cb-form-validation/ts/app/template/hero-form-template1.component.html','name-with-error-msg','template/hero-form-template1.component.html (Hero name)')(format='.')
:marked
Note the following:
- - The `
` element implements the validation rules as HTML validation attributes: `required`, `minlength`, and `maxlength`.
+ - The `
` element carries the HTML validation attributes: `required`, `minlength`, and `maxlength`.
- - Set the `name` attribute of the input box so Angular can track this input element.
+ - We set the `name` attribute of the input box to `"name"` so Angular can track this input element and associate it
+ with an Angular form control called `name` in its internal control model.
- - The `[(ngModel)]` two-way data binding to the hero's name in the `model.name` property also
- registers the input box as a control associated with the implicit `NgForm` directive.
+ - We use the `[(ngModel)]` directive to two-way data bind the input box to the `hero.name` property.
- - A template variable (`#name`) is a reference to this control.
- that we can check for control states such as `valid` or `dirty`.
- The template variable value is always `ngModel`.
+ - We set a template variable (`#name`) to the value `"ngModel"` (always `ngModel`).
+ This gives us a reference to the Angular `NgModel` directive
+ associated with this control that we can use _in the template_
+ to check for control states such as `valid` and `dirty`.
- - A `
` element for a group of validation error messages.
- The `*ngIf` reveals the error group if there are any errors and
+ - The `*ngIf` on `
` element reveals a set of nested message `divs` but only if there are "name" errors and
the control is either `dirty` or `touched`.
- - Within the error group are separate `
` elements for each possible validation error.
- Here we've prepared messages for `required`, `minlength`, and `maxlength`.
+ - Each nested `
` can present a custom message for one of the possible validation errors.
+ We've prepared messages for `required`, `minlength`, and `maxlength`.
The full template repeats this kind of layout for each data entry control on the form.
.l-sub-section
:marked
+ #### Why check _dirty_ and _touched_?
+
We shouldn't show errors for a new hero before the user has had a chance to edit the value.
The checks for `dirty` and `touched` prevent premature display of errors.
Learn about `dirty` and `touched` in the [Forms](../guide/forms.html) chapter.
:marked
- The component class manages the hero model used in the data binding
- as well as other code to support the view.
+ The component class manages the hero model used in the data binding
+ as well as other code to support the view.
-+makeExample('cb-form-validation/ts/app/template/hero-form-template.component.ts','class','app/template/hero-form-template.component.ts')
++makeExample('cb-form-validation/ts/app/template/hero-form-template1.component.ts','class','template/hero-form-template1.component.ts (class)')
:marked
- Use this template-driven validation technique when working with simple forms with simple validation scenarios.
+ Use this template-driven validation technique when working with static forms with simple, standard validation rules.
- Here are the pertinent files for the template-driven approach:
+ Here are the complete files for the first version of `HeroFormTemplateCompononent` in the template-driven approach:
-+makeTabs(
- `cb-form-validation/ts/app/template/hero-form-template.module.ts,
- cb-form-validation/ts/app/template/hero-form-template.component.html,
- cb-form-validation/ts/app/template/hero-form-template.component.ts,
- cb-form-validation/ts/app/shared/hero.ts,
- cb-form-validation/ts/app/shared/submitted.component.ts`,
++makeTabs(
+ `cb-form-validation/ts/app/template/hero-form-template1.component.html,
+ cb-form-validation/ts/app/template/hero-form-template1.component.ts`,
'',
- `app/template/hero-form-template.module.ts,
- app/template/hero-form-template.component.html,
- app/template/hero-form-template.component.ts,
- app/shared/hero.ts,
- app/shared/submitted.component.ts`)
+ `template/hero-form-template1.component.html,
+ template/hero-form-template1.component.ts`)
.l-main-section
-
+a#template2
:marked
- ## Reactive Forms
+ ## Template-Driven Forms with validation messages in code
- Reactive forms are an alternate approach to form validation in the validation rules are specified in the model as
- defined in the component class. Defining the validation in the class instead of the template gives you more control.
- You can adjust the validation based on the application state or user.
- Your code then becomes the source of truth for your validation.
+ While the layout is straightforward,
+ there are obvious shortcomings with the way we handle validation messages:
- We also remove the data binding (`ngModel`) and validation messages from the template.
- This means that we need to set the default value for each control, and we need to add code
- that tracks the user's changes so we can hide/show validation messages as needed.
+ * It takes a lot of HTML to represent all possible error conditions.
+ This gets out of hand when there are many controls and many validation rules.
+
+ * We're not fond of so much JavaScript logic in HTML.
-.alert.is-important
+ * The messages are static strings, hard-coded into the template.
+ We often require dynamic messages that we should shape in code.
+
+ We can move the logic and the messages into the component with a few changes to
+ the template and component.
+
+ Here's the hero name again, excerpted from the revised template ("Template 2"), next to the original version:
++makeTabs(
+ `cb-form-validation/ts/app/template/hero-form-template2.component.html,
+ cb-form-validation/ts/app/template/hero-form-template1.component.html`,
+ 'name-with-error-msg, name-with-error-msg',
+ `hero-form-template2.component.html (name #2),
+ hero-form-template1.component.html (name #1)`)
+
+:marked
+ The `
` element HTML is almost the same. There are noteworthy differences:
+ - The hard-code error message `
` are gone.
+
+ - There's a new attribute, `forbiddenName`, that is actually a custom validation directive.
+ It invalidates the control if the user enters "bob" anywhere in the name ([try it](#live-example)).
+ We discuss [custom validation directives](#custom-validation) later in this cookbook.
+
+ - The `#name` template variable is gone because we no longer refer to the Angular control for this element.
+
+ - Binding to the new `formErrors.name` property is sufficent to display all name validation error messages.
+
+ #### Component class
+ The original component code stays the same.
+ We _added_ new code to acquire the Angular form control and compose error messages.
+
+ The first step is to acquire the form control that Angular created from the template by querying for it.
+
+ Look back at the top of the component template where we set the
+ `#heroForm` template variable in the `