docs(aio): add cross field validation example (#23743)

PR Close #23743
This commit is contained in:
Tomasz Kula
2018-05-07 12:41:07 +02:00
committed by Jason Aden
parent cf0968f98e
commit 002a5afa98
12 changed files with 373 additions and 68 deletions

View File

@ -7,7 +7,7 @@ import { AppComponent } from './app.component';
import { HeroFormTemplateComponent } from './template/hero-form-template.component';
import { HeroFormReactiveComponent } from './reactive/hero-form-reactive.component';
import { ForbiddenValidatorDirective } from './shared/forbidden-name.directive';
import { IdentityRevealedValidatorDirective } from './shared/identity-revealed.directive';
@NgModule({
imports: [
@ -19,7 +19,8 @@ import { ForbiddenValidatorDirective } from './shared/forbidden-name.directive';
AppComponent,
HeroFormTemplateComponent,
HeroFormReactiveComponent,
ForbiddenValidatorDirective
ForbiddenValidatorDirective,
IdentityRevealedValidatorDirective
],
bootstrap: [ AppComponent ]
})

View File

@ -0,0 +1,42 @@
/* tslint:disable: member-ordering forin */
// #docplaster
// #docregion
import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { forbiddenNameValidator } from '../shared/forbidden-name.directive';
@Component({
selector: 'app-hero-form-reactive',
templateUrl: './hero-form-reactive.component.html',
styleUrls: ['./hero-form-reactive.component.css'],
})
export class HeroFormReactiveComponent implements OnInit {
powers = ['Really Smart', 'Super Flexible', 'Weather Changer'];
hero = {name: 'Dr.', alterEgo: 'Dr. What', power: this.powers[0]};
heroForm: FormGroup;
// #docregion form-group
ngOnInit(): void {
// #docregion custom-validator
this.heroForm = new FormGroup({
'name': new FormControl(this.hero.name, [
Validators.required,
Validators.minLength(4),
forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator.
]),
'alterEgo': new FormControl(this.hero.alterEgo),
'power': new FormControl(this.hero.power, Validators.required)
});
// #enddocregion custom-validator
}
get name() { return this.heroForm.get('name'); }
get power() { return this.heroForm.get('power'); }
// #enddocregion form-group
}
// #enddocregion

View File

@ -0,0 +1,5 @@
/* #docregion cross-validation-error-css */
.cross-validation-error input {
border-left: 5px solid red;
}
/* #enddocregion cross-validation-error-css */

View File

@ -3,38 +3,67 @@
<h1>Reactive Form</h1>
<!-- #docregion cross-validation -->
<form [formGroup]="heroForm" #formDir="ngForm">
<!-- #enddocregion cross-validation -->
<div [hidden]="formDir.submitted">
<div class="form-group">
<!-- #docregion cross-validation -->
<!-- #docregion cross-validation-error-class -->
<div class="cross-validation" [class.cross-validation-error]="heroForm.errors?.identityRevealed && (heroForm.touched || heroForm.dirty)">
<!-- #enddocregion cross-validation-error-class -->
<!-- #enddocregion cross-validation -->
<div class="form-group">
<label for="name">Name</label>
<!-- #docregion name-with-error-msg -->
<input id="name" class="form-control"
formControlName="name" required >
<label for="name">Name</label>
<!-- #docregion name-with-error-msg -->
<!-- #docregion cross-validation -->
<!-- #docregion cross-validation-error-class-->
<input id="name" class="form-control"
formControlName="name" required >
<!-- #enddocregion cross-validation -->
<!-- #enddocregion cross-validation-error-class-->
<div *ngIf="name.invalid && (name.dirty || name.touched)"
class="alert alert-danger">
<div *ngIf="name.invalid && (name.dirty || name.touched)"
class="alert alert-danger">
<div *ngIf="name.errors.required">
Name is required.
</div>
<div *ngIf="name.errors.minlength">
Name must be at least 4 characters long.
</div>
<div *ngIf="name.errors.forbiddenName">
Name cannot be Bob.
<div *ngIf="name.errors.required">
Name is required.
</div>
<div *ngIf="name.errors.minlength">
Name must be at least 4 characters long.
</div>
<div *ngIf="name.errors.forbiddenName">
Name cannot be Bob.
</div>
</div>
<!-- #enddocregion name-with-error-msg -->
</div>
<!-- #enddocregion name-with-error-msg -->
</div>
<div class="form-group">
<label for="alterEgo">Alter Ego</label>
<input id="alterEgo" class="form-control"
formControlName="alterEgo" >
<div class="form-group">
<label for="alterEgo">Alter Ego</label>
<!-- #docregion cross-validation -->
<!-- #docregion cross-validation-error-class-->
<input id="alterEgo" class="form-control"
formControlName="alterEgo" >
<!-- #enddocregion cross-validation -->
<!-- #enddocregion cross-validation-error-class -->
</div>
<!-- #docregion cross-validation-error-message -->
<!-- #docregion cross-validation -->
<div *ngIf="heroForm.errors?.identityRevealed && (heroForm.touched || heroForm.dirty)" class="cross-validation-error-message alert alert-danger">
Name cannot match alter ego.
</div>
<!-- #enddocregion cross-validation -->
<!-- #enddocregion cross-validation-error-message -->
<!-- #docregion cross-validation -->
<!-- #docregion cross-validation-error-class-->
</div>
<!-- #enddocregion cross-validation -->
<!-- #enddocregion cross-validation-error-class -->
<div class="form-group">
<label for="power">Hero Power</label>
@ -53,7 +82,9 @@
<button type="button" class="btn btn-default"
(click)="formDir.resetForm({})">Reset</button>
</div>
<!-- #docregion cross-validation -->
</form>
<!-- #enddocregion cross-validation -->
<div class="submitted-message" *ngIf="formDir.submitted">
<p>You've submitted your hero, {{ heroForm.value.name }}!</p>

View File

@ -1,40 +1,44 @@
/* tslint:disable: member-ordering forin */
// #docplaster
// #docregion
import { Component, OnInit } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { forbiddenNameValidator } from '../shared/forbidden-name.directive';
import { identityRevealedValidator } from '../shared/identity-revealed.directive';
// #docregion cross-validation-component
@Component({
selector: 'app-hero-form-reactive',
templateUrl: './hero-form-reactive.component.html'
templateUrl: './hero-form-reactive.component.html',
styleUrls: ['./hero-form-reactive.component.css'],
})
export class HeroFormReactiveComponent implements OnInit {
// #enddocregion cross-validation-component
powers = ['Really Smart', 'Super Flexible', 'Weather Changer'];
hero = {name: 'Dr.', alterEgo: 'Dr. What', power: this.powers[0]};
hero = { name: 'Dr.', alterEgo: 'Dr. What', power: this.powers[0] };
// #docregion cross-validation-component
heroForm: FormGroup;
// #docregion form-group
// #docregion cross-validation-register
ngOnInit(): void {
// #docregion custom-validator
this.heroForm = new FormGroup({
'name': new FormControl(this.hero.name, [
Validators.required,
Validators.minLength(4),
forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator.
forbiddenNameValidator(/bob/i)
]),
'alterEgo': new FormControl(this.hero.alterEgo),
'power': new FormControl(this.hero.power, Validators.required)
});
// #enddocregion custom-validator
}, { validators: identityRevealedValidator }); // <-- add custom validator at the FormGroup level
}
// #enddocregion cross-validation-register
// #enddocregion cross-validation-component
get name() { return this.heroForm.get('name'); }
get power() { return this.heroForm.get('power'); }
// #enddocregion form-group
// #docregion cross-validation-component
}
// #enddocregion
// #enddocregion cross-validation-component

View File

@ -0,0 +1,28 @@
// #docregion
import { Directive } from '@angular/core';
import { AbstractControl, FormGroup, NG_VALIDATORS, ValidationErrors, Validator, ValidatorFn } from '@angular/forms';
// #docregion cross-validation-directive-with-validator
// #docregion cross-validation-validator
/** A hero's name can't match the hero's alter ego */
export const identityRevealedValidator: ValidatorFn = (control: FormGroup): ValidationErrors | null => {
const name = control.get('name');
const alterEgo = control.get('alterEgo');
return name && alterEgo && name.value !== alterEgo.value ? null : { 'identityRevealed': { value: true } };
};
// #enddocregion cross-validation-validator
// #docregion cross-validation-directive
@Directive({
selector: '[appIdentityRevealed]',
providers: [{ provide: NG_VALIDATORS, useExisting: IdentityRevealedValidatorDirective, multi: true }]
})
export class IdentityRevealedValidatorDirective implements Validator {
validate(control: AbstractControl): ValidationErrors {
return identityRevealedValidator(control)
}
}
// #enddocregion cross-validation-directive
// #enddocregion cross-validation-directive-with-validator

View File

@ -0,0 +1,4 @@
/* #docregion */
.cross-validation-error input {
border-left: 5px solid red;
}

View File

@ -2,42 +2,71 @@
<div class="container">
<h1>Template-Driven Form</h1>
<!-- #docregion form-tag-->
<form #heroForm="ngForm">
<!-- #enddocregion form-tag-->
<!-- #docregion cross-validation -->
<!-- #docregion cross-validation-register-validator -->
<form #heroForm="ngForm" appIdentityRevealed>
<!-- #enddocregion cross-validation-register-validator -->
<!-- #enddocregion cross-validation -->
<div [hidden]="heroForm.submitted">
<!-- #docregion cross-validation -->
<!-- #docregion cross-validation-error-class -->
<div class="cross-validation" [class.cross-validation-error]="heroForm.errors?.identityRevealed && (heroForm.touched || heroForm.dirty)">
<!-- #enddocregion cross-validation-error-class -->
<!-- #enddocregion cross-validation -->
<div class="form-group">
<label for="name">Name</label>
<!-- #docregion name-with-error-msg -->
<!-- #docregion name-input -->
<!-- #docregion cross-validation -->
<!-- #docregion cross-validation-error-class -->
<input id="name" name="name" class="form-control"
required minlength="4" appForbiddenName="bob"
[(ngModel)]="hero.name" #name="ngModel" >
<!-- #enddocregion cross-validation-error-class-->
<!-- #enddocregion cross-validation-->
<!-- #enddocregion name-input -->
<div class="form-group">
<label for="name">Name</label>
<!-- #docregion name-with-error-msg -->
<!-- #docregion name-input -->
<input id="name" name="name" class="form-control"
required minlength="4" appForbiddenName="bob"
[(ngModel)]="hero.name" #name="ngModel" >
<!-- #enddocregion name-input -->
<div *ngIf="name.invalid && (name.dirty || name.touched)"
class="alert alert-danger">
<div *ngIf="name.invalid && (name.dirty || name.touched)"
class="alert alert-danger">
<div *ngIf="name.errors.required">
Name is required.
</div>
<div *ngIf="name.errors.minlength">
Name must be at least 4 characters long.
</div>
<div *ngIf="name.errors.forbiddenName">
Name cannot be Bob.
</div>
<div *ngIf="name.errors.required">
Name is required.
</div>
<div *ngIf="name.errors.minlength">
Name must be at least 4 characters long.
</div>
<div *ngIf="name.errors.forbiddenName">
Name cannot be Bob.
</div>
<!-- #enddocregion name-with-error-msg -->
</div>
<!-- #enddocregion name-with-error-msg -->
</div>
<div class="form-group">
<label for="alterEgo">Alter Ego</label>
<input id="alterEgo" class="form-control"
name="alterEgo" [(ngModel)]="hero.alterEgo" >
<div class="form-group">
<label for="alterEgo">Alter Ego</label>
<!-- #docregion cross-validation -->
<!-- #docregion cross-validation-error-class -->
<input id="alterEgo" class="form-control"
name="alterEgo" [(ngModel)]="hero.alterEgo" >
<!-- #enddocregion cross-validation-error-class -->
<!-- #enddocregion cross-validation -->
</div>
<!-- #docregion cross-validation -->
<!-- #docregion cross-validation-error-message -->
<div *ngIf="heroForm.errors?.identityRevealed && (heroForm.touched || heroForm.dirty)" class="cross-validation-error-message alert alert-danger">
Name cannot match alter ego.
</div>
<!-- #enddocregion cross-validation-error-message -->
<!-- #enddocregion cross-validation -->
<!-- #docregion cross-validation-->
<!-- #docregion cross-validation-error-class -->
</div>
<!-- #enddocregion cross-validation-error-class -->
<!-- #enddocregion cross-validation -->
<div class="form-group">
<label for="power">Hero Power</label>
@ -61,6 +90,9 @@
<p>You've submitted your hero, {{ heroForm.value.name }}!</p>
<button (click)="heroForm.resetForm({})">Add new hero</button>
</div>
<!-- #docregion cross-validation -->
</form>
<!-- #enddocregion cross-validation -->
</div>

View File

@ -3,9 +3,11 @@
// #docregion
import { Component } from '@angular/core';
// #docregion component
@Component({
selector: 'app-hero-form-template',
templateUrl: './hero-form-template.component.html'
templateUrl: './hero-form-template.component.html',
styleUrls: ['./hero-form-template.component.css'],
})
export class HeroFormTemplateComponent {
@ -14,3 +16,4 @@ export class HeroFormTemplateComponent {
hero = {name: 'Dr.', alterEgo: 'Dr. What', power: this.powers[0]};
}
// #enddocregion