diff --git a/public/docs/_examples/cb-form-validation/ts/app/template/hero-form-template1.component.ts b/public/docs/_examples/cb-form-validation/ts/app/template/hero-form-template1.component.ts
new file mode 100644
index 0000000000..845fcc9abc
--- /dev/null
+++ b/public/docs/_examples/cb-form-validation/ts/app/template/hero-form-template1.component.ts
@@ -0,0 +1,48 @@
+/* tslint:disable: member-ordering */
+// #docplaster
+// #docregion
+import { Component } from '@angular/core';
+
+
+import { Hero } from '../shared/hero';
+
+@Component({
+ moduleId: module.id,
+ selector: 'hero-form-template1',
+ templateUrl: 'hero-form-template1.component.html'
+})
+// #docregion class
+export class HeroFormTemplate1Component {
+
+ 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 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
+
+ addHero() {
+ this.hero = new Hero(42, '', '');
+// #enddocregion class
+// #enddocregion
+
+ this.active = false;
+ setTimeout(() => this.active = true, 0);
+// #docregion
+// #docregion class
+ }
+}
+// #enddocregion class
+// #enddocregion
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/example-config.json b/public/docs/_examples/cb-form-validation/ts/example-config.json
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/public/docs/_examples/cb-form-validation/ts/forms.css b/public/docs/_examples/cb-form-validation/ts/forms.css
new file mode 100644
index 0000000000..e95fd4bf8d
--- /dev/null
+++ b/public/docs/_examples/cb-form-validation/ts/forms.css
@@ -0,0 +1,11 @@
+.ng-valid[required] {
+ border-left: 5px solid #42A948; /* green */
+}
+
+.ng-invalid {
+ border-left: 5px solid #a94442; /* red */
+}
+
+.ng-valid.required {
+ border-left: 5px solid #42A948; /* green */
+}
\ No newline at end of file
diff --git a/public/docs/_examples/cb-form-validation/ts/index.html b/public/docs/_examples/cb-form-validation/ts/index.html
new file mode 100644
index 0000000000..6aea5beaa4
--- /dev/null
+++ b/public/docs/_examples/cb-form-validation/ts/index.html
@@ -0,0 +1,29 @@
+
+
+ Hero Form with Validation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Loading...
+
+
+
diff --git a/public/docs/_examples/cb-form-validation/ts/plnkr.json b/public/docs/_examples/cb-form-validation/ts/plnkr.json
new file mode 100644
index 0000000000..aa676ad767
--- /dev/null
+++ b/public/docs/_examples/cb-form-validation/ts/plnkr.json
@@ -0,0 +1,7 @@
+{
+ "description": "Validation",
+ "files":[
+ "!**/*.d.ts",
+ "!**/*.js"
+ ]
+}
\ No newline at end of file
diff --git a/public/docs/dart/latest/cookbook/_data.json b/public/docs/dart/latest/cookbook/_data.json
index 9ea1a5f8bf..931857b846 100644
--- a/public/docs/dart/latest/cookbook/_data.json
+++ b/public/docs/dart/latest/cookbook/_data.json
@@ -41,6 +41,12 @@
"hide": true
},
+ "form-validation": {
+ "title": "Form Validation",
+ "intro": "Validate user's form entries",
+ "hide": true
+ },
+
"rc4-to-rc5": {
"title": "RC4 to RC5 Migration",
"intro": "Migrate your RC4 app to RC5 in minutes.",
diff --git a/public/docs/dart/latest/cookbook/dynamic-form.jade b/public/docs/dart/latest/cookbook/dynamic-form.jade
index f8df2a84a6..6778b6af28 100644
--- a/public/docs/dart/latest/cookbook/dynamic-form.jade
+++ b/public/docs/dart/latest/cookbook/dynamic-form.jade
@@ -1 +1 @@
-!= partial("../../../_includes/_ts-temp")
\ No newline at end of file
+!= partial("../../../_includes/_ts-temp")
diff --git a/public/docs/dart/latest/cookbook/form-validation.jade b/public/docs/dart/latest/cookbook/form-validation.jade
new file mode 100644
index 0000000000..6778b6af28
--- /dev/null
+++ b/public/docs/dart/latest/cookbook/form-validation.jade
@@ -0,0 +1 @@
+!= partial("../../../_includes/_ts-temp")
diff --git a/public/docs/js/latest/cookbook/_data.json b/public/docs/js/latest/cookbook/_data.json
index 9a43659334..7443923155 100644
--- a/public/docs/js/latest/cookbook/_data.json
+++ b/public/docs/js/latest/cookbook/_data.json
@@ -37,6 +37,11 @@
"intro": "Render dynamic forms with NgFormModel"
},
+ "form-validation": {
+ "title": "Form Validation",
+ "intro": "Validate user's form entries"
+ },
+
"rc4-to-rc5": {
"title": "RC4 to RC5 Migration",
"intro": "Migrate your RC4 app to RC5 in minutes.",
diff --git a/public/docs/js/latest/cookbook/form-validation.jade b/public/docs/js/latest/cookbook/form-validation.jade
new file mode 100644
index 0000000000..6778b6af28
--- /dev/null
+++ b/public/docs/js/latest/cookbook/form-validation.jade
@@ -0,0 +1 @@
+!= partial("../../../_includes/_ts-temp")
diff --git a/public/docs/ts/latest/cookbook/_data.json b/public/docs/ts/latest/cookbook/_data.json
index f82d816cef..6142c4c6f4 100644
--- a/public/docs/ts/latest/cookbook/_data.json
+++ b/public/docs/ts/latest/cookbook/_data.json
@@ -43,6 +43,11 @@
"intro": "Render dynamic forms with FormGroup"
},
+ "form-validation": {
+ "title": "Form Validation",
+ "intro": "Validate user's form entries"
+ },
+
"rc4-to-rc5": {
"title": "RC4 to RC5 Migration",
"intro": "Migrate your RC4 app to RC5 in minutes."
diff --git a/public/docs/ts/latest/cookbook/form-validation.jade b/public/docs/ts/latest/cookbook/form-validation.jade
new file mode 100644
index 0000000000..877026adcc
--- /dev/null
+++ b/public/docs/ts/latest/cookbook/form-validation.jade
@@ -0,0 +1,462 @@
+include ../_util-fns
+
+a#top
+:marked
+ 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.
+.l-sub-section
+ :marked
+ Learn more about these choices in the [Forms chapter.](../guide/forms.html)
+
+a#toc
+:marked
+ ## Contents
+
+ [Simple Template-Driven Forms](#template1)
+
+ [Template-Driven Forms with validation messages in code](#template2)
+
+ [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
+ ## Simple Template-Driven Forms
+
+ 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-template1.component.html','name-with-error-msg','template/hero-form-template1.component.html (Hero name)')(format='.')
+
+:marked
+ Note the following:
+ - The `` element carries the HTML validation attributes: `required`, `minlength`, and `maxlength`.
+
+ - 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.
+
+ - We use the `[(ngModel)]` directive to two-way data bind the input box to the `hero.name` property.
+
+ - 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`.
+
+ - 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`.
+
+ - 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.
+
++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 static forms with simple, standard validation rules.
+
+ 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-template1.component.html,
+ cb-form-validation/ts/app/template/hero-form-template1.component.ts`,
+ '',
+ `template/hero-form-template1.component.html,
+ template/hero-form-template1.component.ts`)
+
+.l-main-section
+a#template2
+:marked
+ ## Template-Driven Forms with validation messages in code
+
+ While the layout is straightforward,
+ there are obvious shortcomings with the way we handle validation messages:
+
+ * 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.
+
+ * 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 `