diff --git a/public/docs/_examples/cb-validation/e2e-spec.ts b/public/docs/_examples/cb-form-validation/e2e-spec.ts similarity index 100% rename from public/docs/_examples/cb-validation/e2e-spec.ts rename to public/docs/_examples/cb-form-validation/e2e-spec.ts diff --git a/public/docs/_examples/cb-validation/ts/app/app.component.ts b/public/docs/_examples/cb-form-validation/ts/app/app.component.ts similarity index 71% rename from public/docs/_examples/cb-validation/ts/app/app.component.ts rename to public/docs/_examples/cb-form-validation/ts/app/app.component.ts index 1dcd5a9d5a..5260b8d9e1 100644 --- a/public/docs/_examples/cb-validation/ts/app/app.component.ts +++ b/public/docs/_examples/cb-form-validation/ts/app/app.component.ts @@ -1,4 +1,3 @@ -// #docplaster // #docregion import { Component } from '@angular/core'; @@ -6,7 +5,6 @@ import { Component } from '@angular/core'; selector: 'my-app', template: `
- ` + ` }) export class AppComponent { } -// #enddocregion \ No newline at end of file diff --git a/public/docs/_examples/cb-form-validation/ts/app/app.module.ts b/public/docs/_examples/cb-form-validation/ts/app/app.module.ts new file mode 100644 index 0000000000..72b4e3a770 --- /dev/null +++ b/public/docs/_examples/cb-form-validation/ts/app/app.module.ts @@ -0,0 +1,18 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import { AppComponent } from './app.component'; +import { HeroFormTemplateModule } from './template/hero-form-template.module'; +import { HeroFormReactiveModule } from './reactive/hero-form-reactive.module'; + +@NgModule({ + imports: [ + BrowserModule, + HeroFormTemplateModule, + HeroFormReactiveModule + ], + declarations: [ AppComponent ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/public/docs/_examples/cb-validation/ts/app/main.ts b/public/docs/_examples/cb-form-validation/ts/app/main.ts similarity index 85% rename from public/docs/_examples/cb-validation/ts/app/main.ts rename to public/docs/_examples/cb-form-validation/ts/app/main.ts index eea33fea8e..961a226688 100644 --- a/public/docs/_examples/cb-validation/ts/app/main.ts +++ b/public/docs/_examples/cb-form-validation/ts/app/main.ts @@ -1,8 +1,6 @@ -// #docplaster // #docregion import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app.module'; platformBrowserDynamic().bootstrapModule(AppModule); -// #enddocregion \ No newline at end of file diff --git a/public/docs/_examples/cb-form-validation/ts/app/reactive/hero-form-reactive.component.html b/public/docs/_examples/cb-form-validation/ts/app/reactive/hero-form-reactive.component.html new file mode 100644 index 0000000000..8b7f81f0d7 --- /dev/null +++ b/public/docs/_examples/cb-form-validation/ts/app/reactive/hero-form-reactive.component.html @@ -0,0 +1,45 @@ + +
+
+

Hero Form (Reactive)

+
+
+ + + +
+ {{ formError.name }} +
+ +
+ +
+ + +
+ +
+ + +
+ {{ formError.power }} +
+
+ + + +
+
+ + +
diff --git a/public/docs/_examples/cb-form-validation/ts/app/reactive/hero-form-reactive.component.ts b/public/docs/_examples/cb-form-validation/ts/app/reactive/hero-form-reactive.component.ts new file mode 100644 index 0000000000..c61d3ef05d --- /dev/null +++ b/public/docs/_examples/cb-form-validation/ts/app/reactive/hero-form-reactive.component.ts @@ -0,0 +1,107 @@ +/* tslint:disable: member-ordering forin */ +// #docplaster +// #docregion +import { Component, OnInit } from '@angular/core'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; + +import { Hero } from '../shared/hero'; + +@Component({ + moduleId: module.id, + selector: 'hero-form-reactive', + templateUrl: 'hero-form-reactive.component.html' +}) +// #docregion class +export class HeroFormReactiveComponent implements OnInit { + + powers = ['Really Smart', 'Super Flexible', 'Weather Changer']; + + model = new Hero(18, 'Dr. WhatIsHisWayTooLongName', this.powers[0], 'Dr. What'); + + submitted = false; + + onSubmit() { + this.submitted = true; + this.model = this.heroForm.value; + } +// #enddocregion class + + // 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) + // #docregion new-hero + active = true; + +// #docregion class + newHero() { + this.model = new Hero(42, '', ''); + this.buildForm(); + this.onValueChanged(''); +// #enddocregion class + + this.active = false; + setTimeout(() => this.active = true, 0); +// #docregion class + } + + //// New with Reactive Form + + heroForm: FormGroup; + constructor(private builder: FormBuilder) { } + + ngOnInit(): void { this.buildForm(); } + + formError = { + 'name': '', + 'power': '' + }; + + validationMessages = { + 'name': { + 'required': 'Name is required.', + 'minlength': 'Name must be at least 4 characters long.', + 'maxlength': 'Name cannot be more than 24 characters long.' + }, + 'power': { + 'required': 'Power is required.' + } + }; + + buildForm(): void { + this.heroForm = this.builder.group({ + 'name': [this.model.name, [ + Validators.required, + Validators.minLength(4), + Validators.maxLength(24) + ] + ], + 'alterEgo': [this.model.alterEgo], + 'power': [this.model.power, Validators.required] + }); + this.heroForm.valueChanges + .subscribe(data => this.onValueChanged(data)); + } + + onValueChanged(data: any) { + const controls = this.heroForm ? this.heroForm.controls : {}; + for (const field in this.formError) { + // clear previous error message (if any) + this.formError[field] = ''; + const control = controls[field]; + if (control && control.dirty && !control.valid) { + const messages = this.validationMessages[field]; + for (const key in control.errors) { + this.formError[field] += messages[key] + ' '; + } + } + } + } + + isRequired(controlName: string): boolean { + const msgs = this.validationMessages[controlName]; + return msgs && msgs['required']; + } +} +// #enddocregion class +// #enddocregion diff --git a/public/docs/_examples/cb-form-validation/ts/app/reactive/hero-form-reactive.module.ts b/public/docs/_examples/cb-form-validation/ts/app/reactive/hero-form-reactive.module.ts new file mode 100644 index 0000000000..dd9aecda74 --- /dev/null +++ b/public/docs/_examples/cb-form-validation/ts/app/reactive/hero-form-reactive.module.ts @@ -0,0 +1,13 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { ReactiveFormsModule } from '@angular/forms'; + +import { SharedModule } from '../shared/shared.module'; +import { HeroFormReactiveComponent } from './hero-form-reactive.component'; + +@NgModule({ + imports: [ SharedModule, ReactiveFormsModule ], + declarations: [ HeroFormReactiveComponent ], + exports: [ HeroFormReactiveComponent ] +}) +export class HeroFormReactiveModule { } diff --git a/public/docs/_examples/cb-validation/ts/app/hero.ts b/public/docs/_examples/cb-form-validation/ts/app/shared/hero.ts similarity index 82% rename from public/docs/_examples/cb-validation/ts/app/hero.ts rename to public/docs/_examples/cb-form-validation/ts/app/shared/hero.ts index 3556a791c1..fe2b55e51a 100644 --- a/public/docs/_examples/cb-validation/ts/app/hero.ts +++ b/public/docs/_examples/cb-form-validation/ts/app/shared/hero.ts @@ -1,13 +1,9 @@ -// #docplaster // #docregion export class Hero { - constructor( public id: number, public name: string, public power: string, public alterEgo?: string ) { } - } -// #enddocregion diff --git a/public/docs/_examples/cb-form-validation/ts/app/shared/shared.module.ts b/public/docs/_examples/cb-form-validation/ts/app/shared/shared.module.ts new file mode 100644 index 0000000000..fd6c35ef2e --- /dev/null +++ b/public/docs/_examples/cb-form-validation/ts/app/shared/shared.module.ts @@ -0,0 +1,12 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { SubmittedComponent } from './submitted.component'; + +@NgModule({ + imports: [ CommonModule], + declarations: [ SubmittedComponent ], + exports: [ CommonModule, SubmittedComponent ] +}) +export class SharedModule { } diff --git a/public/docs/_examples/cb-form-validation/ts/app/shared/submitted.component.ts b/public/docs/_examples/cb-form-validation/ts/app/shared/submitted.component.ts new file mode 100644 index 0000000000..18cea6563f --- /dev/null +++ b/public/docs/_examples/cb-form-validation/ts/app/shared/submitted.component.ts @@ -0,0 +1,32 @@ +// #docregion +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +import { Hero } from './hero'; + +@Component({ + selector: 'hero-submitted', + template: ` +
+

You submitted the following:

+
+
Name
+
{{ hero.name }}
+
+
+
Alter Ego
+
{{ hero.alterEgo }}
+
+
+
Power
+
{{ hero.power }}
+
+
+ +
` +}) +export class SubmittedComponent { + @Input() hero: Hero; + @Input() submitted = false; + @Output() submittedChange = new EventEmitter(); + onClick() { this.submittedChange.emit(false); } +} diff --git a/public/docs/_examples/cb-validation/ts/app/hero-form-template.component.html b/public/docs/_examples/cb-form-validation/ts/app/template/hero-form-template.component.html similarity index 64% rename from public/docs/_examples/cb-validation/ts/app/hero-form-template.component.html rename to public/docs/_examples/cb-form-validation/ts/app/template/hero-form-template.component.html index e18f72c4c4..575036701e 100644 --- a/public/docs/_examples/cb-validation/ts/app/hero-form-template.component.html +++ b/public/docs/_examples/cb-form-validation/ts/app/template/hero-form-template.component.html @@ -1,19 +1,18 @@ -

Hero Form (Template-Driven)

-
- -
Name is required @@ -47,27 +46,12 @@
- - + +
-
-

You submitted the following:

-
-
Name
-
{{ model.name }}
-
-
-
Alter Ego
-
{{ model.alterEgo }}
-
-
-
Power
-
{{ model.power }}
-
-
- -
+
- diff --git a/public/docs/_examples/cb-validation/ts/app/hero-form-template.component.ts b/public/docs/_examples/cb-form-validation/ts/app/template/hero-form-template.component.ts similarity index 58% rename from public/docs/_examples/cb-validation/ts/app/hero-form-template.component.ts rename to public/docs/_examples/cb-form-validation/ts/app/template/hero-form-template.component.ts index d0a76ce246..9149ad3126 100644 --- a/public/docs/_examples/cb-validation/ts/app/hero-form-template.component.ts +++ b/public/docs/_examples/cb-form-validation/ts/app/template/hero-form-template.component.ts @@ -1,38 +1,45 @@ +/* tslint:disable: member-ordering */ // #docplaster // #docregion import { Component } from '@angular/core'; -import { Hero } from './hero'; + +import { Hero } from '../shared/hero'; @Component({ + moduleId: module.id, selector: 'hero-form-template', - templateUrl: 'app/hero-form-template.component.html' + templateUrl: 'hero-form-template.component.html' }) // #docregion class export class HeroFormTemplateComponent { - powers = ['Really Smart', 'Super Flexible', - 'Super Hot', 'Weather Changer']; + powers = ['Really Smart', 'Super Flexible', 'Weather Changer']; + + model = new Hero(18, 'Dr. WhatIsHisWayTooLongName', this.powers[0], 'Dr. What'); - model = new Hero(18, 'Dr IQ', this.powers[0], - 'Chuck Overstreet'); -// #enddocregion class submitted = false; - onSubmit() { this.submitted = true; } + onSubmit() { + this.submitted = true; + } +// #enddocregion class // 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 class newHero() { this.model = new Hero(42, '', ''); +// #enddocregion class + this.active = false; - setTimeout(()=> this.active=true, 0); - } + setTimeout(() => this.active = true, 0); // #docregion class + } } // #enddocregion class // #enddocregion diff --git a/public/docs/_examples/cb-form-validation/ts/app/template/hero-form-template.module.ts b/public/docs/_examples/cb-form-validation/ts/app/template/hero-form-template.module.ts new file mode 100644 index 0000000000..800ba19643 --- /dev/null +++ b/public/docs/_examples/cb-form-validation/ts/app/template/hero-form-template.module.ts @@ -0,0 +1,13 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; + +import { SharedModule } from '../shared/shared.module'; +import { HeroFormTemplateComponent } from './hero-form-template.component'; + +@NgModule({ + imports: [ SharedModule, FormsModule ], + declarations: [ HeroFormTemplateComponent ], + exports: [ HeroFormTemplateComponent ] +}) +export class HeroFormTemplateModule { } diff --git a/public/docs/_examples/cb-validation/ts/example-config.json b/public/docs/_examples/cb-form-validation/ts/example-config.json similarity index 100% rename from public/docs/_examples/cb-validation/ts/example-config.json rename to public/docs/_examples/cb-form-validation/ts/example-config.json diff --git a/public/docs/_examples/cb-validation/ts/forms.css b/public/docs/_examples/cb-form-validation/ts/forms.css similarity index 100% rename from public/docs/_examples/cb-validation/ts/forms.css rename to public/docs/_examples/cb-form-validation/ts/forms.css diff --git a/public/docs/_examples/cb-validation/ts/index.html b/public/docs/_examples/cb-form-validation/ts/index.html similarity index 100% rename from public/docs/_examples/cb-validation/ts/index.html rename to public/docs/_examples/cb-form-validation/ts/index.html diff --git a/public/docs/_examples/cb-validation/ts/plnkr.json b/public/docs/_examples/cb-form-validation/ts/plnkr.json similarity index 100% rename from public/docs/_examples/cb-validation/ts/plnkr.json rename to public/docs/_examples/cb-form-validation/ts/plnkr.json diff --git a/public/docs/_examples/cb-validation/ts/app/app.module.ts b/public/docs/_examples/cb-validation/ts/app/app.module.ts deleted file mode 100644 index d20f416716..0000000000 --- a/public/docs/_examples/cb-validation/ts/app/app.module.ts +++ /dev/null @@ -1,25 +0,0 @@ -// #docregion -import { NgModule } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; -import { FormsModule } from '@angular/forms'; -import { ReactiveFormsModule } from '@angular/forms'; - -import { AppComponent } from './app.component'; -import { HeroFormTemplateComponent } from './hero-form-template.component' -import { HeroFormModelComponent } from './hero-form-model.component' - -@NgModule({ - imports: [ - BrowserModule, - FormsModule, - ReactiveFormsModule - ], - declarations: [ - AppComponent, - HeroFormTemplateComponent, - HeroFormModelComponent - ], - bootstrap: [ AppComponent ] -}) -export class AppModule { } -// #enddocregion diff --git a/public/docs/_examples/cb-validation/ts/app/hero-form-model.component.html b/public/docs/_examples/cb-validation/ts/app/hero-form-model.component.html deleted file mode 100644 index 9dc4b65635..0000000000 --- a/public/docs/_examples/cb-validation/ts/app/hero-form-model.component.html +++ /dev/null @@ -1,61 +0,0 @@ - - -
-
-

Hero Form (Model-Driven)

-
-
- - - -
- {{ formError.name }} -
- -
- -
- - -
- -
- - -
- {{ formError.power }} -
-
- - - -
-
- -
-

You submitted the following:

-
-
Name
-
{{ model.name }}
-
-
-
Alter Ego
-
{{ model.alterEgo }}
-
-
-
Power
-
{{ model.power }}
-
-
- -
-
- diff --git a/public/docs/_examples/cb-validation/ts/app/hero-form-model.component.ts b/public/docs/_examples/cb-validation/ts/app/hero-form-model.component.ts deleted file mode 100644 index 11037c7dd3..0000000000 --- a/public/docs/_examples/cb-validation/ts/app/hero-form-model.component.ts +++ /dev/null @@ -1,107 +0,0 @@ -// #docplaster -// #docregion -import { Component, OnInit } from '@angular/core'; -import { FormGroup, FormBuilder, Validators } from '@angular/forms'; - -import { Hero } from './hero'; - -@Component({ - selector: 'hero-form-model', - templateUrl: 'app/hero-form-model.component.html' -}) -// #docregion class -export class HeroFormModelComponent implements OnInit { - heroForm: FormGroup; - formError: { [id: string]: string }; - private validationMessages: { [id: string]: { [id: string]: string } }; - - powers = ['Really Smart', 'Super Flexible', - 'Super Hot', 'Weather Changer']; - - model = new Hero(18, 'Dr IQ', this.powers[0], - 'Chuck Overstreet'); - -// #enddocregion class - submitted = false; -// #docregion class - constructor(private fb: FormBuilder) { - this.formError = { - 'name': '', - 'alterEgo': '', - 'power': '' - }; - this.validationMessages = { - 'name': { - 'required': 'Name is required.', - 'minlength': 'Name must be at least 4 characters long.', - 'maxlength': 'Name cannot be more than 24 characters long.' - }, - 'power': { - 'required': 'Power is required.' - } - }; - } - - ngOnInit(): void { - this.buildForm(); - } - - buildForm(): void { - this.heroForm = this.fb.group({ - 'name': [this.model.name, - [Validators.required, - Validators.minLength(4), - Validators.maxLength(24)]], - 'alterEgo': [this.model.alterEgo], - 'power': [this.model.power, Validators.required] - }); - - this.heroForm.valueChanges - .subscribe(data => this.onValueChanged(data)); - } - - onValueChanged(data: any) { - for (let field in this.formError) { - if (this.formError.hasOwnProperty(field)) { - let hasError = (this.heroForm.controls[field].dirty) && - !this.heroForm.controls[field].valid; - this.formError[field] = ''; - if (hasError) { - for (let key in this.heroForm.controls[field].errors) { - if (this.heroForm.controls[field].errors.hasOwnProperty(key)) { - this.formError[field] += this.validationMessages[field][key] + ' '; - } - } - } - } - } - } -// #enddocregion class - onSubmit() { - this.submitted = true; - this.model = this.heroForm.value; - } - - isRequired(controlName: string): boolean { - if (Object.keys(this.validationMessages).includes(controlName)) { - return Object.keys(this.validationMessages[controlName]).includes('required');} - return false; - } - // 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) - // #docregion new-hero - active = true; - - newHero() { - this.model = new Hero(42, '', ''); - this.buildForm(); - this.onValueChanged(''); - this.active = false; - setTimeout(() => this.active = true, 0); - } - // #docregion class -} -// #enddocregion class -// #enddocregion 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 4cc4187bc7..6142c4c6f4 100644 --- a/public/docs/ts/latest/cookbook/_data.json +++ b/public/docs/ts/latest/cookbook/_data.json @@ -43,16 +43,16 @@ "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." }, - "validation": { - "title": "Validation", - "intro": "Validate user's form entries" - }, - "set-document-title": { "title": "Set the Document Title", "intro": "Setting the document or window title using the Title service." diff --git a/public/docs/ts/latest/cookbook/validation.jade b/public/docs/ts/latest/cookbook/form-validation.jade similarity index 56% rename from public/docs/ts/latest/cookbook/validation.jade rename to public/docs/ts/latest/cookbook/form-validation.jade index 4cccdbbb13..ba0e9693de 100644 --- a/public/docs/ts/latest/cookbook/validation.jade +++ b/public/docs/ts/latest/cookbook/form-validation.jade @@ -2,91 +2,97 @@ include ../_util-fns :marked - We want our data to be accurate and complete. By helping the user enter - appropriate data and confirming that data is valid, we can improve the quality of that incoming data. - - In this cookbook we show how to validate data and display useful validation messages using first the - template-driven forms and then the reactive forms approach. + We can improve overall data quality by validating user input for accuracy and completeness. - An Angular component is comprised of a template and a component class containing the code that drives the template. - The first example demonstrates how to validate data using only the template. The second technique - moves the validation logic out of the template and into the component class, giving you more control and better unit testing. + 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. + + 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. - In these examples we use the form created in the [Forms chapter.](../guide/forms.html) + Both examples are based on the the sample form in the [Forms chapter.](../guide/forms.html) :marked - ## Table of contents + ## Contents - [Template-Driven Forms Approach](#template-driven) + [Template-Driven Forms Approach](#template-driven) - [Reactive Forms Approach](#model-driven) + [Reactive Forms Approach](#reactive) -:marked - **See the [live example](/resources/live-examples/cb-validation/ts/plnkr.html)**. + **Try the live example** + .l-main-section :marked - ## Template-Driven Forms Approach + ## Template-Driven Forms - Using the template-driven approach to form validation, the validation is defined in the template. - Each control on the form defines its validation, binding, and validation messages in the template. + In the template-driven approach, + each control on the form defines its own validation and validation messages in the template. -+makeExample('cb-validation/ts/app/hero-form-template.component.html','name-with-error-msg','app/hero-form-template.component.html') + 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)') :marked - Here we define a standard label and set up an input box for validation of the hero name as follows: - - Add the desired HTML validation attributes. In this example, - we add `required`, `minlength`, and `maxlength` attributes on the input box to define the validation rules. - - Set the `name` attribute of the input box. This is required for Angular to track this input element. - - Use `ngModel` binding to set the default value and track the user's changes to the input box. - This registers the input box as a control that is associated with the `ngForm` directive. - - Define a template reference variable (`name` in this example) that references the registered control. - We use this variable to reference this control when checking the control state, such as `valid` or `dirty`. + Note the following: + - The `` element implements the validation rules as HTML validation attributes: `required`, `minlength`, and `maxlength`. - Next we define a `div` element for the validation error messages. - We use the template reference variable to determine whether to display one of the validation messages. - - We use `*ngIf` to check whether the control has errors and whether it is `dirty` or `touched` before - displaying the validation block `div`. - - We then have a separate `div` for each possible validation error. In our example, we defined validation for - `required`, `minlength`, and `maxlength`, so we add one `div` for each one. Each `div` is marked with `[hidden]` - so the validation message is hidden unless the control has the specified error. + - Set the `name` attribute of the input box so Angular can track this input element. - Repeat for each data entry control on the form. + - 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. + + - 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`. + + - A `
` element for a group of validation error messages. + The `*ngIf` reveals the error group if there are any 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`. + + The full template repeats this kind of layout for each data entry control on the form. .l-sub-section :marked - Adding the check for `dirty` or `touched` prevents display of errors before the user has a chance to edit the - value. This is most useful when adding new data, such as a new hero. + 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 model used in the data binding. And provides any other code required by 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-validation/ts/app/hero-form-template.component.ts','class','app/hero-form-template.component.ts') ++makeExample('cb-form-validation/ts/app/template/hero-form-template.component.ts','class','app/template/hero-form-template.component.ts') :marked Use this template-driven validation technique when working with simple forms with simple validation scenarios. - Here's the complete solution for the template-driven approach: + Here are the pertinent files for the template-driven approach: +makeTabs( - `cb-validation/ts/app/main.ts, - cb-validation/ts/app/app.module.ts, - cb-validation/ts/app/app.component.ts, - cb-validation/ts/app/hero.ts, - cb-validation/ts/app/hero-form-template.component.html, - cb-validation/ts/app/hero-form-template.component.ts`, + `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`, '', - 'app/main.ts, app/app.module.ts, app/app.component.ts, app/hero.ts, app/hero-form-template.component.html, app/hero-form-template.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`) .l-main-section - + :marked - ## Reactive Forms Approach + ## Reactive Forms - The alternate way to implement forms in Angular is to use reactive forms, previously called `model-driven forms`. - Using the reactive forms approach to form validation, the validation rules are specified in the model as + 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. @@ -100,7 +106,7 @@ include ../_util-fns When moving the validation attributes out of the HTML, we are no longer aria ready. Work is being done to address this. -+makeExample('cb-validation/ts/app/hero-form-model.component.ts','class','app/hero-form-model.component.ts') ++makeExample('cb-form-validation/ts/app/reactive/hero-form-reactive.component.ts','class','app/reactive/hero-form-reactive.component.ts') :marked In the component's class, we define the form and our own data structures to manage definition and display of the validation messages: @@ -153,7 +159,7 @@ include ../_util-fns We'll use the form and `formError` collection properties in the template. Notice that when using the reactive forms approach, the amount of code required for each control in the template is signficantly reduced. -+makeExample('cb-validation/ts/app/hero-form-model.component.html','name-with-error-msg','app/hero-form-model.component.html') ++makeExample('cb-form-validation/ts/app/reactive/hero-form-reactive.component.html','name-with-error-msg','app/reactive/hero-form-reactive.component.html') :marked In the template, define a standard label and set up an input box for validation as follows: @@ -173,17 +179,20 @@ include ../_util-fns Use this technique when you want better control over the validation rules and messsages. - Here's the complete solution for the reactive forms approach: + Here are the pertinent files for the reactive forms approach: +makeTabs( - `cb-validation/ts/app/main.ts, - cb-validation/ts/app/app.module.ts, - cb-validation/ts/app/app.component.ts, - cb-validation/ts/app/hero.ts, - cb-validation/ts/app/hero-form-model.component.html, - cb-validation/ts/app/hero-form-model.component.ts`, + `cb-form-validation/ts/app/reactive/hero-form-reactive.module.ts, + cb-form-validation/ts/app/reactive/hero-form-reactive.component.html, + cb-form-validation/ts/app/reactive/hero-form-reactive.component.ts, + cb-form-validation/ts/app/shared/hero.ts, + cb-form-validation/ts/app/shared/submitted.component.ts`, '', - 'app/main.ts, app/app.module.ts, app/app.component.ts, app/hero.ts, app/hero-form-model.component.html, app/hero-form-model.component.ts' ) + `app/reactive/hero-form-reactive.module.ts, + app/reactive/hero-form-reactive.component.html, + app/reactive/hero-form-reactive.component.ts, + app/shared/hero.ts, + app/shared/submitted.component.ts`) :marked - [Back to top](#top) \ No newline at end of file + [Back to top](#top) diff --git a/public/resources/images/cookbooks/form-validation/plunker.png b/public/resources/images/cookbooks/form-validation/plunker.png new file mode 100644 index 0000000000..0df5840a52 Binary files /dev/null and b/public/resources/images/cookbooks/form-validation/plunker.png differ