14. Forms: Reactive Forms
DataModel:
export class Technology {
id = 0;
name = '';
descriptions: Descriptions[];
}
export class Descriptions {
developer = '';
initialRelease = '';
stableRelease = '';
details = '';
}
export const technologies: Technology[] = [
{
id: 1,
name: 'Angular',
descriptions: [
{ developer: 'Google', initialRelease: 'October 20 2010', stableRelease: '1.6.6 / August 18 2017', details: 'SuperHeroic JavaScript MV* Open Source Framework.' },
{ developer: 'Google', initialRelease: 'September 14 2016', stableRelease: '4.4.4 / 28 September 2017', details: 'One Framework. Mobile & desktop. Open Source.'}
]
},
{
id: 2,
name: 'Angular CLI',
descriptions: [
{ developer: 'Google', initialRelease: 'December 4 2015', stableRelease: '1.4.9 / October 23 2017', details: 'Command Line Interface for Angular, tool to initialize, scaffold, and maintain Angular applications.'}
]
},
{
id: 3,
name: 'Angular Material',
descriptions: []
}
];
export const developer = [ 'Google', 'Humanity', 'Open Source', 'GitHub'];
TechnologyReactiveDetailComponent1:
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
@Component({
selector: 'app-technology-reactive-detail',
template: `
<h2>Technology Reactive Detail</h2>
<h3><i>Just a FormControl</i></h3>
<label class="center-block">Name:
<input class="form-control" [formControl]="name">
</label>
`
})
export class TechnologyReactiveDetailComponent1 {
name = new FormControl();
}
AppComponent:
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<h1>{{ title }}</h1>
<div class="left">
<h1>Reactive Forms</h1>
<app-technology-reactive-detail></app-technology-reactive-detail>
</div>
<div class="right">
<img [src]="imageUrl" class="img-round" alt="princess image">
</div>
`,
styles: [`h1 { font-weight: bold; }`]
})
export class AppComponent {
title = 'Tour of Technologies';
imageUrl = '../assets/polymer1.jpg-large';
}
Let's view the running app in Chromium:
FormGroup:
import { Component } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
@Component({
selector: 'app-technology-reactive-detail2',
template: `
<h2>Technology Reactive Detail</h2>
<h3><i>FormControl in a FormGroup</i></h3>
<form [formGroup]="technologyForm" novalidate>
<div class="form-group">
<label class="center-block">Name:
<input class="form-control" formControlName="name">
</label>
</div>
</form>
<p>Form value: {{ technologyForm.value | json }}</p>
<p>Form status: {{ technologyForm.status | json }}</p>
`
})
export class TechnologyReactiveDetailComponent2 {
technologyForm = new FormGroup({
name: new FormControl()
});
}
FormBuilder:
import { Component } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
@Component({
selector: 'app-technology-reactive-detail3',
template: `
<h2>Technology Reactive Detail</h2>
<h3><i>FormGroup with single FormControl using FormBuilder</i></h3>
<form [formGroup]="technologyForm" novalidate>
<div class="form-group">
<label class="center-block">Name:
<input class="form-control" formControlName="name">
</label>
</div>
</form>
<p>Form value: {{ technologyForm.value | json }}</p>
<p>Form status: {{ technologyForm.status | json }}</p>
`
})
export class TechnologyReactiveDetailComponent3 {
technologyForm: FormGroup; // <--- technologyform is of type formgroup
constructor(private fb: FormBuilder) { // <--- inject formbuilder
this.createForm();
}
createForm() {
this.technologyForm = this.fb.group({
name: ['', Validators.required ]
});
}
}
More Form Controls:
import { Component } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { developer } from './data-model';
@Component({
selector: 'app-technology-reactive-detail4',
template: `
<h2>Technology Reactive Detail</h2>
<h3><i>FormGroup with multiple FormControls</i></h3>
<form [formGroup]="technologyForm" novalidate>
<div class="form-group">
<label class="center-block">Name:
<input class="form-control" formControlName="name">
</label>
</div>
<div class="form-group">
<label class="center-block">Initial Release:
<input class="form-control" formControlName="initialRelease">
</label>
</div>
<div class="form-group">
<label class="center-block">Stable Release:
<input class="form-control" formControlName="stableRelease">
</label>
</div>
<div class="form-group">
<label class="center-block">Developer:
<select class="form-control" formControlName="developer">
<option *ngFor="let dev of developer" [value]="dev">{{ dev }}</option>
</select>
</label>
</div>
<div class="form-group">
<label class="center-block">Details:
<input class="form-control" formControlName="details">
</label>
</div>
<div class="form-group radio">
<h4>Super Power:</h4>
<label class="center-block"><input type="radio" formControlName="power"
value="data binding">Data Binding</label>
<label class="center-block"><input type="radio" formControlName="power"
value="animations">Animations</label>
<label class="center-block"><input type="radio" formControlName="power"
value="routing">Routing</label>
<label class="center-block"><input type="radio" formControlName="power"
value="tooling">Tooling</label>
<label class="center-block"><input type="radio" formControlName="power"
value="beautiful css">Beautiful CSS</label>
</div>
<div class="checkbox">
<label class="center-block">
<input type="checkbox" formControlName="sidekick">I have a sidekick.
</label>
</div>
</form>
<p>Form value: {{ technologyForm.value | json }}</p>
`
})
export class TechnologyReactiveDetailComponent4 {
technologyForm: FormGroup;
developer = developer;
constructor(private fb: FormBuilder) {
this.createForm();
}
createForm() {
this.technologyForm = this.fb.group({
name: ['', Validators.required ],
developer: '',
initialRelease: '',
stableRelease: '',
details: '',
power: '',
sidekick: ''
});
}
}
Let's have a look at this form in our Chromium Browser:
Nested FormGroups:
import { Component } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { developer } from './data-model';
@Component({
selector: 'app-technology-reactive-detail5',
template: `
<h2>Technology Reactive Detail</h2>
<h3><i>Nested FormGroups</i></h3>
<form [formGroup]="technologyForm" novalidate>
<div class="form-group">
<label class="center-block">Name:
<input class="form-control" formControlName="name">
</label>
</div>
<div formGroupName="bio" class="card card-body bg-light">
<h4>Secret Data</h4>
<div class="form-group">
<label class="center-block">Initial Release:
<input class="form-control" formControlName="initialRelease">
</label>
</div>
<div class="form-group">
<label class="center-block">Stable Release:
<input class="form-control" formControlName="stableRelease">
</label>
</div>
<div class="form-group">
<label class="center-block">Developer:
<select class="form-control" formControlName="developer">
<option *ngFor="let dev of developer" [value]="dev">{{ dev }}</option>
</select>
</label>
</div>
<div class="form-group">
<label class="center-block">Details:
<input class="form-control" formControlName="details">
</label>
</div>
</div>
<div class="form-group radio">
<h4>Super Power:</h4>
<label class="center-block"><input type="radio" formControlName="power"
value="data binding">Data Binding</label>
<label class="center-block"><input type="radio" formControlName="power"
value="animations">Animations</label>
<label class="center-block"><input type="radio" formControlName="power"
value="routing">Routing</label>
<label class="center-block"><input type="radio" formControlName="power"
value="tooling">Tooling</label>
<label class="center-block"><input type="radio" formControlName="power"
value="beautiful css">Beautiful CSS</label>
</div>
<div class="checkbox">
<label class="center-block">
<input type="checkbox" formControlName="sidekick">I have a sidekick.
</label>
</div>
</form>
<p>Form value: {{ technologyForm.value | json }}</p>
<h4>Extra info for the curious:</h4>
<p>Name value: {{ technologyForm.get('name').value }}</p>
<p>Details value: {{ technologyForm.get('bio.details').value }}</p>
`
})
export class TechnologyReactiveDetailComponent5 {
technologyForm: FormGroup;
developer = developer;
constructor(private fb: FormBuilder) {
this.createForm();
}
createForm() {
this.technologyForm = this.fb.group({ // <--- parent formgroup
name: ['', Validators.required ],
bio: this.fb.group({ // <-- child formgroup
developer: '',
initialRelease: '',
stableRelease: '',
details: ''
}),
power: '',
sidekick: ''
});
}
}
Populate Form Model with setValue
TechnologyReactiveService:
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import 'rxjs/add/operator/delay';
import { Technology, technologies } from './data-model';
@Injectable()
export class TechnologyReactiveService {
delayMs = 1000;
getTechnologies(): Observable<Technology[]> {
return of(technologies).delay(this.delayMs);
}
updateTechnology(technology: Technology): Observable<Technology> {
const oldTechnology = technologies.find(t => t.id === technology.id);
const newTechnology = Object.assign(oldTechnology, technology);
return of (newTechnology).delay(this.delayMs);
}
}
TechnologyReactiveListComponent:
import { Component, OnInit } from '@angular/core';
import { Technology } from './data-model';
import { TechnologyReactiveService } from './technology-reactive.service';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/finally';
@Component({
selector: 'app-technology-reactive-list',
template: `
<h3 *ngIf="isLoading"><i>Loading technologies ...</i></h3>
<app-technology-reactive-detail7
*ngFor="let technology of technologies | async"
[technology]="technology">
</app-technology-reactive-detail7>
`
})
export class TechnologyReactiveListComponent implements OnInit {
technologies: Observable<Technology[]>;
isLoading = false;
constructor(private technologyReactiveService: TechnologyReactiveService) { }
ngOnInit() {
this.getTechnologies();
}
getTechnologies() {
this.isLoading = true;
this.technologies = this.technologyReactiveService.getTechnologies()
.finally(() => this.isLoading = false);
}
}
TechnologyReactiveDetailComponent with setValue:
import { Component, Input, OnChanges } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { Technology, Descriptions, developer } from './data-model';
@Component({
selector: 'app-technology-reactive-detail7',
template: `
<h3><i>Technology Reactive Secret Details</i></h3>
<form [formGroup]="technologyForm" novalidate>
<div class="form-group">
<label class="center-block">Name:
<input class="form-control" formControlName="name">
</label>
</div>
<div formGroupName="bio" class="card card-body bg-light">
<h4>Secret Data</h4>
<div class="form-group">
<label class="center-block">Initial Release:
<input class="form-control" formControlName="initialRelease">
</label>
</div>
<div class="form-group">
<label class="center-block">Stable Release:
<input class="form-control" formControlName="stableRelease">
</label>
</div>
<div class="form-group">
<label class="center-block">Developer:
<select class="form-control" formControlName="developer">
<option *ngFor="let dev of developer" [value]="dev">{{ dev }}</option>
</select>
</label>
</div>
<div class="form-group">
<label class="center-block">Details:
<input class="form-control" formControlName="details">
</label>
</div>
</div>
<div class="form-group radio">
<h4>Super Power:</h4>
<label class="center-block"><input type="radio" formControlName="power"
value="data binding">Data Binding</label>
<label class="center-block"><input type="radio" formControlName="power"
value="animations">Animations</label>
<label class="center-block"><input type="radio" formControlName="power"
value="routing">Routing</label>
<label class="center-block"><input type="radio" formControlName="power"
value="tooling">Tooling</label>
<label class="center-block"><input type="radio" formControlName="power"
value="beautiful css">Beautiful CSS</label>
</div>
<div class="checkbox">
<label class="center-block">
<input type="checkbox" formControlName="sidekick">I have a sidekick.
</label>
</div>
</form>
<p>Form value: {{ technologyForm.value | json }}</p>
`
})
export class TechnologyReactiveDetailComponent7 implements OnChanges {
@Input() technology: Technology;
technologyForm: FormGroup;
developer = developer;
constructor(private fb: FormBuilder) {
this.createForm();
}
createForm() {
this.technologyForm = this.fb.group({
name: ['', Validators.required ],
bio: this.fb.group(new Descriptions()), // <-- formgroup with a new descriptions
power: '',
sidekick: ''
});
}
ngOnChanges() {
this.technologyForm.reset();
this.technologyForm.setValue({
name: this.technology.name,
bio: this.technology.descriptions[0] || new Descriptions(),
power: '',
sidekick: ''
});
}
}
AppComponent with my Polymer Princess(hidden):
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<h1>{{ title }}</h1>
<div class="left">
<h1>Reactive Forms</h1>
<app-technology-reactive-list></app-technology-reactive-list>
</div>
<div class="right">
<img [src]="imageUrl" class="img-round" alt="princess image">
</div>
`,
styles: [`h1 { font-weight: bold; }`]
})
export class AppComponent {
title = 'Tour of Technologies';
imageUrl = '../assets/polymer6.jpg';
}
Let's have a look at this beauty in Chromium:
patchValue:
ngOnChanges() {
this.technologyForm.reset();
this.technologyForm.patchValue({
name: this.technology.name,
bio: this.technology.descriptions[0] || new Descriptions()
});
}
FormArray:
import { Component, Input, OnChanges } from '@angular/core';
import { FormArray, FormGroup, FormBuilder, Validators } from '@angular/forms';
import { Technology, Descriptions, developer } from './data-model';
@Component({
selector: 'app-technology-reactive-detail8',
template: `
<h3><i>Technology Reactive Secret Details</i></h3>
<form [formGroup]="technologyForm" novalidate>
<p>Form Changed: {{ technologyForm.dirty }}</p>
<div class="form-group">
<label class="center-block">Name:
<input class="form-control" formControlName="name">
</label>
</div>
<div formArrayName="secretBios" class="card card-body bg-light">
<div *ngFor="let bios of secretBios.controls; let i = index;" [formGroupName]="i">
<!-- repeated bios template -->
<h4>BIOS #{{ i + 1}}</h4>
<div style="margin-left: 1em;">
<div class="form-group">
<label class="center-block">Initial Release:
<input class="form-control" formControlName="initialRelease">
</label>
</div>
<div class="form-group">
<label class="center-block">Stable Release:
<input class="form-control" formControlName="stableRelease">
</label>
</div>
<div class="form-group">
<label class="center-block">Developer:
<select class="form-control" formControlName="developer">
<option *ngFor="let dev of developer" [value]="dev">{{ dev }}</option>
</select>
</label>
</div>
<div class="form-group">
<label class="center-block">Details:
<textarea rows="2" cols="50" class="form-control" formControlName="details"></textarea>
</label>
</div>
<button (click)="removeBios(i)" type="button">Remove Bios</button>
</div>
<br>
<!-- end of repeated bios template -->
</div>
<button (click)="addBios()" type="button">Add a Secret Bios</button>
</div>
<div class="form-group radio">
<h4>Super Power:</h4>
<label class="center-block"><input type="radio" formControlName="power"
value="data binding">Data Binding</label>
<label class="center-block"><input type="radio" formControlName="power"
value="animations">Animations</label>
<label class="center-block"><input type="radio" formControlName="power"
value="routing">Routing</label>
<label class="center-block"><input type="radio" formControlName="power"
value="tooling">Tooling</label>
<label class="center-block"><input type="radio" formControlName="power"
value="beautiful css">Beautiful CSS</label>
</div>
<div class="checkbox">
<label class="center-block">
<input type="checkbox" formControlName="sidekick">I have a sidekick.
</label>
</div>
</form>
<p>technologyForm value: {{ technologyForm.value | json }}</p>
<h4>Name change log</h4>
<div *ngFor="let name of nameChangeLog">{{ name }}</div>
`
})
export class TechnologyReactiveDetailComponent8 implements OnChanges {
@Input() technology: Technology;
technologyForm: FormGroup;
developer = developer;
nameChangeLog: string[] = [];
constructor(private fb: FormBuilder) {
this.createForm();
this.logNameChange();
}
createForm() {
this.technologyForm = this.fb.group({
name: ['', Validators.required ],
secretBios: this.fb.array([]), // <-- secretBios as an empty formarray
power: '',
sidekick: ''
});
}
logNameChange() {
const nameControl = this.technologyForm.get('name');
nameControl.valueChanges.forEach((value: string) => this.nameChangeLog.push(value));
}
ngOnChanges() {
this.technologyForm.reset({
name: this.technology.name
});
this.setBioses(this.technology.descriptions);
}
setBioses(descriptions: Descriptions[]) {
const biosFGs = descriptions.map(description => this.fb.group(description));
const biosFormArray = this.fb.array(biosFGs);
this.technologyForm.setControl('secretBios', biosFormArray);
}
get secretBios(): FormArray {
return this.technologyForm.get('secretBios') as FormArray;
}
addBios() {
this.secretBios.push(this.fb.group(new Descriptions()));
}
removeBios(index: number) {
this.secretBios.removeAt(index);
}
}
Let's view the running application in Chromium:
Save Form Data:
import { Component, Input, OnChanges } from '@angular/core';
import { FormArray, FormGroup, FormBuilder, Validators } from '@angular/forms';
import { TechnologyReactiveService } from './technology-reactive.service';
import { Technology, Descriptions, developer } from './data-model';
@Component({
selector: 'app-technology-reactive-detail',
template: `
<h3><i>Technology Reactive Secret Details</i></h3>
<form [formGroup]="technologyForm" (ngSubmit)="onSubmit()" novalidate>
<div style="margin-bottom: 1em;">
<button type="submit" [disabled]="technologyForm.pristine" class="btn btn-success">
Save
</button>
<button type="reset" (click)="revert()" [disabled]="technologyForm.pristine"
class="btn btn-danger">
Revert
</button>
</div>
<div class="form-group">
<label class="center-block">Name:
<input class="form-control" formControlName="name">
</label>
</div>
<div formArrayName="secretBios" class="card card-body bg-light">
<div *ngFor="let bios of secretBios.controls; let i = index;" [formGroupName]="i">
<!-- repeated bios template -->
<h4>BIOS #{{ i + 1}}</h4>
<div style="margin-left: 1em;">
<div class="form-group">
<label class="center-block">Initial Release:
<input class="form-control" formControlName="initialRelease">
</label>
</div>
<div class="form-group">
<label class="center-block">Stable Release:
<input class="form-control" formControlName="stableRelease">
</label>
</div>
<div class="form-group">
<label class="center-block">Developer:
<select class="form-control" formControlName="developer">
<option *ngFor="let dev of developer" [value]="dev">{{ dev }}</option>
</select>
</label>
</div>
<div class="form-group">
<label class="center-block">Details:
<textarea rows="2" cols="50" class="form-control" formControlName="details"></textarea>
</label>
</div>
<button (click)="removeBios(i)" type="button">Remove Bios</button>
</div>
<br>
<!-- end of repeated bios template -->
</div>
<button (click)="addBios()" type="button">Add a Secret Bios</button>
</div>
<div class="form-group radio">
<h4>Super Power:</h4>
<label class="center-block"><input type="radio" formControlName="power"
value="data binding">Data Binding</label>
<label class="center-block"><input type="radio" formControlName="power"
value="animations">Animations</label>
<label class="center-block"><input type="radio" formControlName="power"
value="routing">Routing</label>
<label class="center-block"><input type="radio" formControlName="power"
value="tooling">Tooling</label>
<label class="center-block"><input type="radio" formControlName="power"
value="beautiful css">Beautiful CSS</label>
</div>
<div class="checkbox">
<label class="center-block">
<input type="checkbox" formControlName="sidekick">I have a sidekick.
</label>
</div>
</form>
<p>technologyForm value: {{ technologyForm.value | json }}</p>
<h4>Name change log</h4>
<div *ngFor="let name of nameChangeLog">{{ name }}</div>
`
})
export class TechnologyReactiveDetailComponent implements OnChanges {
@Input() technology: Technology;
technologyForm: FormGroup;
developer = developer;
nameChangeLog: string[] = [];
constructor(private fb: FormBuilder, private technologyReactiveService: TechnologyReactiveService) {
this.createForm();
this.logNameChange();
}
createForm() {
this.technologyForm = this.fb.group({
name: ['', Validators.required ],
secretBios: this.fb.array([]), // <-- secretBios as an empty formarray
power: '',
sidekick: ''
});
}
logNameChange() {
const nameControl = this.technologyForm.get('name');
nameControl.valueChanges.forEach((value: string) => this.nameChangeLog.push(value));
}
ngOnChanges() {
this.technologyForm.reset({
name: this.technology.name
});
this.setBioses(this.technology.descriptions);
}
setBioses(descriptions: Descriptions[]) {
const biosFGs = descriptions.map(description => this.fb.group(description));
const biosFormArray = this.fb.array(biosFGs);
this.technologyForm.setControl('secretBios', biosFormArray);
}
get secretBios(): FormArray {
return this.technologyForm.get('secretBios') as FormArray;
}
addBios() {
this.secretBios.push(this.fb.group(new Descriptions()));
}
removeBios(index: number) {
this.secretBios.removeAt(index);
}
revert() {
this.ngOnChanges();
}
onSubmit() {
this.technology = this.prepareSaveTechnology();
this.technologyReactiveService.updateTechnology(this.technology).subscribe(/* error handling*/);
this.ngOnChanges();
}
prepareSaveTechnology(): Technology {
const formModel = this.technologyForm.value;
// deep copy of form model bios
const secretBiosDeepCopy: Descriptions[] = formModel.secretBios.map(
(description: Descriptions) => Object.assign({}, description)
);
// return new technology object containing a combination of original technology value(s)
// and deep copies of changed form model values
const saveTechnology: Technology = {
id: this.technology.id,
name: formModel.name as string,
descriptions: secretBiosDeepCopy
};
return saveTechnology;
}
}
Let's view the results and test the app in our Chromium Web Browser:
LoginComponent:
import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators, AbstractControl, ValidationErrors } from '@angular/forms';
export class UserNameValidators {
static cannotContainSpace(control: AbstractControl): ValidationErrors | null {
if ((control.value as string).indexOf(' ') !== -1) {
return { cannotContainSpace: true };
}
return null;
}
static shouldBeUnique(control: AbstractControl): Promise<ValidationErrors | null> {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (control.value === 'nils-holger') {
resolve({ shouldBeUnique: true });
} else {
resolve(null);
}
}, 1500);
});
}
}
@Component({
selector: 'app-login',
template: `
<form [formGroup]="form" (ngSubmit)="login()">
<div *ngIf="form.errors" class="alert alert-danger">
Username and/or password are invalid.
</div>
<div formGroupName="account">
<div class="form-group">
<label for="username">Username:</label>
<input formControlName="username" id="username" type="text" class="form-control">
<div *ngIf="username.pending">
<i class="fa fa-spinner fa-pulse fa-lg fa-fw"></i>
<span class="sr-only">Loading...</span>
</div>
<div *ngIf="username.touched && username.invalid"
class="alert alert-danger">
<div *ngIf="username.errors.required">Username is required.</div>
<div *ngIf="username.errors.minlength">
Username should be minimum {{ username.errors.minlength.requiredLength }} characters.
</div>
<div *ngIf="username.errors.cannotContainSpace">Username cannot contain space.</div>
<div *ngIf="username.errors.shouldBeUnique">Username is already taken.</div>
</div>
</div>
<div class="form-group">
<label for="password">Password:</label>
<input formControlName="password" id="password" type="password" class="form-control">
<div *ngIf="password.pending">
Checking password ...
</div>
<div *ngIf="password.touched && password.invalid"
class="alert alert-danger">
<div *ngIf="password.errors.required">Password is required.</div>
<div *ngIf="password.errors.minlength">
Password should be minimum {{ password.errors.minlength.requiredLength }} characters.
</div>
<div *ngIf="password.errors.cannotContainSpace">Password cannot contain space.</div>
<div *ngIf="password.errors.shouldBeUnique">Password is already taken.</div>
</div>
</div>
</div>
<button class="btn btn-primary" [disabled]="!form.valid" type="submit">Log In</button>
</form>
`,
styles: [`
.form-control.ng-touched.ng-invalid {
border-left: 5px solid red;
}
.form-control.ng-touched,ng-dirty.ng-valid {
border-left: 5px solid green;
}
`]
})
export class LoginComponent {
form = new FormGroup({
account: new FormGroup({
username: new FormControl('', [
Validators.required,
Validators.minLength(5),
UserNameValidators.cannotContainSpace
], UserNameValidators.shouldBeUnique),
password: new FormControl('', [
Validators.required,
Validators.minLength(7),
UserNameValidators.cannotContainSpace
], UserNameValidators.shouldBeUnique)
})
});
get username() {
return this.form.get('account.username');
}
get password() {
return this.form.get('account.password');
}
login() {
this.form.setErrors({
invalidLogin: true
});
}
}
Let's view this form in our Chromium Web Browser:
NewTechnologyFormComponent:
import { Component } from '@angular/core';
import { FormGroup, FormArray, FormControl } from '@angular/forms';
@Component({
selector: 'app-new-technology-form-component',
template: `
<form>
<input #box
type="text"
class="form-control"
(keyup.enter)="add(box)">
<ul class="list-group">
<li *ngFor="let component of components.controls;
first as isFirst; last as isLast;
even as isEven; odd as isOdd;"
(click)="remove(component)"
class="list-group-item list-group-item-action"
[class.list-group-item-danger]="isEven && !isFirst && !isLast"
[class.list-group-item-warning]="isOdd && !isFirst && !isLast"
[class.list-group-item-primary]="isFirst"
[class.list-group-item-success]="isLast">
{{ component.value }}
</li>
</ul>
</form>
`
})
export class NewTechnologyFormComponent {
form = new FormGroup({
components: new FormArray([])
});
add(component: HTMLInputElement) {
if (component.value) {
this.components.push(new FormControl(component.value));
component.value = '';
}
}
remove(component: FormControl) {
const idx = this.components.controls.indexOf(component);
this.components.removeAt(idx);
}
get components() {
return this.form.get('components') as FormArray;
}
}
Let's view the results in Chromium:
FormBuilder:
export class NewTechnologyFormComponentComponent {
form;
constructor(fb: FormBuilder) {
fb.group({
name: ['', Validators.required],
contact: fb.group({
email: [],
phone: []
}),
components: fb.array([])
});
}
}
ChangePasswordComponent:
import { Component } from '@angular/core';
import { FormGroup, FormBuilder, Validators, AbstractControl, ValidationErrors } from '@angular/forms';
export class ChangePasswordValidators {
static shouldEqual(control: AbstractControl): Promise<ValidationErrors | null> {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (control.value !== '12345') {
resolve({ shouldEqual: true });
} else {
resolve(null);
}
}, 1500);
});
}
static shouldMatch(control: AbstractControl) {
const newPassword = control.get('newpassword');
const confirmPassword = control.get('confirmpassword');
if (newPassword.value !== confirmPassword.value) {
return { shouldMatch: true };
}
return null;
}
}
@Component({
selector: 'app-change-password',
template: `
<form [formGroup]="form" (ngSubmit)="change()">
<div class="form-group">
<label for="oldpassword">Old Password</label>
<input formControlName="oldpassword" type="password" class="form-control">
<div *ngIf="oldpassword.pending">
Validating Password ...
</div>
<div *ngIf="oldpassword.touched && oldpassword.invalid"
class="alert alert-danger">
<div *ngIf="oldpassword.errors.required">Old Password is required.</div>
<div *ngIf="oldpassword.errors.shouldEqual">Old Password is invalid.</div>
</div>
</div>
<div class="form-group">
<label for="newpassword">New Password</label>
<input formControlName="newpassword" type="password" class="form-control">
<div *ngIf="newpassword.touched && newpassword.invalid"
class="alert alert-danger">
<div *ngIf="newpassword.errors.required">New Password is required.</div>
</div>
</div>
<div class="form-group">
<label for="confirmpassword">Confirm Password</label>
<input formControlName="confirmpassword" type="password" class="form-control">
<div *ngIf="confirmpassword.touched && confirmpassword.invalid"
class="alert alert-danger">
<div *ngIf="confirmpassword.errors.required">Confirm Password is required.</div>
</div>
<div *ngIf="confirmpassword.valid && form.invalid && form.errors.shouldMatch" class="alert alert-danger">
Passwords don\'t match.
</div>
</div>
<button class="btn btn-primary" [disabled]="!form.valid" type="submit">Change</button>
</form>
`,
styles: [`
.form-control.ng-touched.ng-invalid {
border-left: 5px solid red;
}
.form-control.ng-touched,ng-dirty.ng-valid {
border-left: 5px solid green;
}
`]
})
export class ChangePasswordComponent {
form: FormGroup;
constructor(fb: FormBuilder) {
this.form = fb.group({
oldpassword: ['', Validators.required, ChangePasswordValidators.shouldEqual],
newpassword: ['', Validators.required],
confirmpassword: ['', Validators.required]
},
{
validator: ChangePasswordValidators.shouldMatch
}
);
}
get oldpassword() {
return this.form.get('oldpassword');
}
get newpassword() {
return this.form.get('newpassword');
}
get confirmpassword() {
return this.form.get('confirmpassword');
}
change() {
// do something here
}
}