docs(cb-form-validation): ward's tweaks

renamed from cb-validation to cb-form-validation
other refactorings and text changes
This commit is contained in:
Ward Bell 2016-08-26 21:02:32 -07:00
parent 3b4a5d533d
commit f971685a7c
28 changed files with 356 additions and 304 deletions

View File

@ -1,4 +1,3 @@
// #docplaster
// #docregion // #docregion
import { Component } from '@angular/core'; import { Component } from '@angular/core';
@ -6,7 +5,6 @@ import { Component } from '@angular/core';
selector: 'my-app', selector: 'my-app',
template: `<hero-form-template></hero-form-template> template: `<hero-form-template></hero-form-template>
<hr> <hr>
<hero-form-model></hero-form-model>` <hero-form-reactive></hero-form-reactive>`
}) })
export class AppComponent { } export class AppComponent { }
// #enddocregion

View File

@ -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 { }

View File

@ -1,8 +1,6 @@
// #docplaster
// #docregion // #docregion
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module'; import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule); platformBrowserDynamic().bootstrapModule(AppModule);
// #enddocregion

View File

@ -0,0 +1,45 @@
<!-- #docregion -->
<div class="container">
<div [hidden]="submitted">
<h1>Hero Form (Reactive)</h1>
<form [formGroup]="heroForm" *ngIf="active" (ngSubmit)="onSubmit()">
<div class="form-group">
<!-- #docregion name-with-error-msg -->
<label for="name">Name</label>
<input type="text" id="name" class="form-control"
formControlName="name"
[ngClass]="{'required': isRequired('name')}">
<div *ngIf="formError.name" class="alert alert-danger">
{{ formError.name }}
</div>
<!-- #enddocregion name-with-error-msg -->
</div>
<div class="form-group">
<label for="alterEgo">Alter Ego</label>
<input type="text" id="alterEgo" class="form-control"
formControlName="alterEgo"
[ngClass]="{'required': isRequired('alterEgo')}" >
</div>
<div class="form-group">
<label for="power">Hero Power</label>
<select id="power" class="form-control"
formControlName="power"
[ngClass]="{'required': isRequired('power')}" >
<option *ngFor="let p of powers" [value]="p">{{p}}</option>
</select>
<div *ngIf="formError.power" class="alert alert-danger">
{{ formError.power }}
</div>
</div>
<button type="submit" class="btn btn-default"
[disabled]="!heroForm.valid">Submit</button>
<button type="button" class="btn btn-default"
(click)="newHero()">New Hero</button>
</form>
</div>
<hero-submitted [hero]="model" [(submitted)]="submitted"></hero-submitted>
</div>

View File

@ -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

View File

@ -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 { }

View File

@ -1,13 +1,9 @@
// #docplaster
// #docregion // #docregion
export class Hero { export class Hero {
constructor( constructor(
public id: number, public id: number,
public name: string, public name: string,
public power: string, public power: string,
public alterEgo?: string public alterEgo?: string
) { } ) { }
} }
// #enddocregion

View File

@ -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 { }

View File

@ -0,0 +1,32 @@
// #docregion
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Hero } from './hero';
@Component({
selector: 'hero-submitted',
template: `
<div *ngIf="submitted">
<h2>You submitted the following:</h2>
<div class="row">
<div class="col-xs-3">Name</div>
<div class="col-xs-9 pull-left">{{ hero.name }}</div>
</div>
<div class="row">
<div class="col-xs-3">Alter Ego</div>
<div class="col-xs-9 pull-left">{{ hero.alterEgo }}</div>
</div>
<div class="row">
<div class="col-xs-3">Power</div>
<div class="col-xs-9 pull-left">{{ hero.power }}</div>
</div>
<br>
<button class="btn btn-default" (click)="onClick()">Edit</button>
</div>`
})
export class SubmittedComponent {
@Input() hero: Hero;
@Input() submitted = false;
@Output() submittedChange = new EventEmitter<boolean>();
onClick() { this.submittedChange.emit(false); }
}

View File

@ -1,4 +1,3 @@
<!-- #docplaster -->
<!-- #docregion --> <!-- #docregion -->
<div class="container"> <div class="container">
<div [hidden]="submitted"> <div [hidden]="submitted">
@ -47,27 +46,12 @@
</div> </div>
</div> </div>
<button type="submit" class="btn btn-default" [disabled]="!heroForm.form.valid">Submit</button> <button type="submit" class="btn btn-default"
<button type="button" class="btn btn-default" (click)="newHero()">New Hero</button> [disabled]="!heroForm.form.valid">Submit</button>
<button type="button" class="btn btn-default"
(click)="newHero()">New Hero</button>
</form> </form>
</div> </div>
<div [hidden]="!submitted"> <hero-submitted [hero]="model" [(submitted)]="submitted"></hero-submitted>
<h2>You submitted the following:</h2>
<div class="row">
<div class="col-xs-3">Name</div>
<div class="col-xs-9 pull-left">{{ model.name }}</div>
</div>
<div class="row">
<div class="col-xs-3">Alter Ego</div>
<div class="col-xs-9 pull-left">{{ model.alterEgo }}</div>
</div>
<div class="row">
<div class="col-xs-3">Power</div>
<div class="col-xs-9 pull-left">{{ model.power }}</div>
</div>
<br>
<button class="btn btn-default" (click)="submitted=false">Edit</button>
</div>
</div> </div>
<!-- #enddocregion -->

View File

@ -1,38 +1,45 @@
/* tslint:disable: member-ordering */
// #docplaster // #docplaster
// #docregion // #docregion
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { Hero } from './hero';
import { Hero } from '../shared/hero';
@Component({ @Component({
moduleId: module.id,
selector: 'hero-form-template', selector: 'hero-form-template',
templateUrl: 'app/hero-form-template.component.html' templateUrl: 'hero-form-template.component.html'
}) })
// #docregion class // #docregion class
export class HeroFormTemplateComponent { export class HeroFormTemplateComponent {
powers = ['Really Smart', 'Super Flexible', powers = ['Really Smart', 'Super Flexible', 'Weather Changer'];
'Super Hot', '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; submitted = false;
onSubmit() { this.submitted = true; } onSubmit() {
this.submitted = true;
}
// #enddocregion class
// Reset the form with a new hero AND restore 'pristine' class state // Reset the form with a new hero AND restore 'pristine' class state
// by toggling 'active' flag which causes the form // by toggling 'active' flag which causes the form
// to be removed/re-added in a tick via NgIf // to be removed/re-added in a tick via NgIf
// TODO: Workaround until NgForm has a reset method (#6822) // TODO: Workaround until NgForm has a reset method (#6822)
active = true; active = true;
// #docregion class
newHero() { newHero() {
this.model = new Hero(42, '', ''); this.model = new Hero(42, '', '');
// #enddocregion class
this.active = false; this.active = false;
setTimeout(()=> this.active=true, 0); setTimeout(() => this.active = true, 0);
}
// #docregion class // #docregion class
}
} }
// #enddocregion class // #enddocregion class
// #enddocregion // #enddocregion

View File

@ -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 { }

View File

@ -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

View File

@ -1,61 +0,0 @@
<!-- #docplaster -->
<!-- #docregion -->
<div class="container">
<div [hidden]="submitted">
<h1>Hero Form (Model-Driven)</h1>
<form [formGroup]="heroForm" *ngIf="active" (ngSubmit)="onSubmit()">
<div class="form-group">
<!-- #docregion name-with-error-msg -->
<label for="name3">Name</label>
<input type="text" id="name3" class="form-control"
formControlName="name"
[ngClass]="{'required': isRequired('name')}">
<div *ngIf="formError.name" class="alert alert-danger">
{{ formError.name }}
</div>
<!-- #enddocregion name-with-error-msg -->
</div>
<div class="form-group">
<label for="alterEgo3">Alter Ego</label>
<input type="text" id="alterEgo3" class="form-control"
formControlName="alterEgo"
[ngClass]="{'required': isRequired('alterEgo')}" >
</div>
<div class="form-group">
<label for="power3">Hero Power</label>
<select id="power3" class="form-control"
formControlName="power"
[ngClass]="{'required': isRequired('power')}" >
<option *ngFor="let p of powers" [value]="p">{{p}}</option>
</select>
<div *ngIf="formError.power" class="alert alert-danger">
{{ formError.power }}
</div>
</div>
<button type="submit" class="btn btn-default" [disabled]="!heroForm.valid">Submit</button>
<button type="button" class="btn btn-default" (click)="newHero()">New Hero</button>
</form>
</div>
<div [hidden]="!submitted">
<h2>You submitted the following:</h2>
<div class="row">
<div class="col-xs-3">Name</div>
<div class="col-xs-9 pull-left">{{ model.name }}</div>
</div>
<div class="row">
<div class="col-xs-3">Alter Ego</div>
<div class="col-xs-9 pull-left">{{ model.alterEgo }}</div>
</div>
<div class="row">
<div class="col-xs-3">Power</div>
<div class="col-xs-9 pull-left">{{ model.power }}</div>
</div>
<br>
<button class="btn btn-default" (click)="submitted=false">Edit</button>
</div>
</div>
<!-- #enddocregion -->

View File

@ -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

View File

@ -41,6 +41,12 @@
"hide": true "hide": true
}, },
"form-validation": {
"title": "Form Validation",
"intro": "Validate user's form entries",
"hide": true
},
"rc4-to-rc5": { "rc4-to-rc5": {
"title": "RC4 to RC5 Migration", "title": "RC4 to RC5 Migration",
"intro": "Migrate your RC4 app to RC5 in minutes.", "intro": "Migrate your RC4 app to RC5 in minutes.",

View File

@ -0,0 +1 @@
!= partial("../../../_includes/_ts-temp")

View File

@ -37,6 +37,11 @@
"intro": "Render dynamic forms with NgFormModel" "intro": "Render dynamic forms with NgFormModel"
}, },
"form-validation": {
"title": "Form Validation",
"intro": "Validate user's form entries"
},
"rc4-to-rc5": { "rc4-to-rc5": {
"title": "RC4 to RC5 Migration", "title": "RC4 to RC5 Migration",
"intro": "Migrate your RC4 app to RC5 in minutes.", "intro": "Migrate your RC4 app to RC5 in minutes.",

View File

@ -0,0 +1 @@
!= partial("../../../_includes/_ts-temp")

View File

@ -43,16 +43,16 @@
"intro": "Render dynamic forms with FormGroup" "intro": "Render dynamic forms with FormGroup"
}, },
"form-validation": {
"title": "Form Validation",
"intro": "Validate user's form entries"
},
"rc4-to-rc5": { "rc4-to-rc5": {
"title": "RC4 to RC5 Migration", "title": "RC4 to RC5 Migration",
"intro": "Migrate your RC4 app to RC5 in minutes." "intro": "Migrate your RC4 app to RC5 in minutes."
}, },
"validation": {
"title": "Validation",
"intro": "Validate user's form entries"
},
"set-document-title": { "set-document-title": {
"title": "Set the Document Title", "title": "Set the Document Title",
"intro": "Setting the document or window title using the Title service." "intro": "Setting the document or window title using the Title service."

View File

@ -2,91 +2,97 @@ include ../_util-fns
<a id="top"></a> <a id="top"></a>
:marked :marked
We want our data to be accurate and complete. By helping the user enter We can improve overall data quality by validating user input for accuracy and completeness.
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 In this cookbook we show how to validate user input in the UI and display useful validation messages
template-driven forms and then the reactive forms approach. using first the template-driven forms and then the reactive forms approach.
An Angular component is comprised of a template and a component class containing the code that drives the template. An Angular component consists 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 The first example demonstrates input validation entirely within the template.
moves the validation logic out of the template and into the component class, giving you more control and better unit testing. 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)
<a id="toc"></a> <a id="toc"></a>
:marked :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 **Try the live example**
**See the [live example](/resources/live-examples/cb-validation/ts/plnkr.html)**. <live-example name="cb-form-validation" embedded img="cookbooks/form-validation/plunker.png"></live-example>
.l-main-section .l-main-section
<a id="template-driven"></a> <a id="template-driven"></a>
:marked :marked
## Template-Driven Forms Approach ## Template-Driven Forms
Using the template-driven approach to form validation, the validation is defined in the template. In the template-driven approach,
Each control on the form defines its validation, binding, and validation messages in the template. 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 :marked
Here we define a standard label and set up an input box for validation of the hero name as follows: Note the following:
- Add the desired HTML validation attributes. In this example, - The `<input>` element implements the validation rules as HTML validation attributes: `required`, `minlength`, and `maxlength`.
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`.
Next we define a `div` element for the validation error messages. - Set the `name` attribute of the input box so Angular can track this input element.
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.
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 `<div>` 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 `<div>` 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 .l-sub-section
:marked :marked
Adding the check for `dirty` or `touched` prevents display of errors before the user has a chance to edit the We shouldn't show errors for a new hero before the user has had a chance to edit the value.
value. This is most useful when adding new data, such as a new hero. The checks for `dirty` and `touched` prevent premature display of errors.
Learn about `dirty` and `touched` in the [Forms](../guide/forms.html) chapter. Learn about `dirty` and `touched` in the [Forms](../guide/forms.html) chapter.
:marked :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 :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 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( +makeTabs(
`cb-validation/ts/app/main.ts, `cb-form-validation/ts/app/template/hero-form-template.module.ts,
cb-validation/ts/app/app.module.ts, cb-form-validation/ts/app/template/hero-form-template.component.html,
cb-validation/ts/app/app.component.ts, cb-form-validation/ts/app/template/hero-form-template.component.ts,
cb-validation/ts/app/hero.ts, cb-form-validation/ts/app/shared/hero.ts,
cb-validation/ts/app/hero-form-template.component.html, cb-form-validation/ts/app/shared/submitted.component.ts`,
cb-validation/ts/app/hero-form-template.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 .l-main-section
<a id="model-driven"></a> <a id="reactive"></a>
:marked :marked
## Reactive Forms Approach ## Reactive Forms
The alternate way to implement forms in Angular is to use reactive forms, previously called `model-driven forms`. Reactive forms are an alternate approach to form validation in the validation rules are specified in the model as
Using the reactive forms approach to form validation, 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. 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. You can adjust the validation based on the application state or user.
Your code then becomes the source of truth for your validation. 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 When moving the validation attributes out of the HTML, we are no longer aria ready. Work is being done to
address this. 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 :marked
In the component's class, we define the form and our own data structures to manage definition and display of the validation messages: 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, 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. 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 :marked
In the template, define a standard label and set up an input box for validation as follows: 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. 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( +makeTabs(
`cb-validation/ts/app/main.ts, `cb-form-validation/ts/app/reactive/hero-form-reactive.module.ts,
cb-validation/ts/app/app.module.ts, cb-form-validation/ts/app/reactive/hero-form-reactive.component.html,
cb-validation/ts/app/app.component.ts, cb-form-validation/ts/app/reactive/hero-form-reactive.component.ts,
cb-validation/ts/app/hero.ts, cb-form-validation/ts/app/shared/hero.ts,
cb-validation/ts/app/hero-form-model.component.html, cb-form-validation/ts/app/shared/submitted.component.ts`,
cb-validation/ts/app/hero-form-model.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 :marked
[Back to top](#top) [Back to top](#top)

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB