6. Template-DRIVEN & Reactive Forms
ngModel simple two way data binding:
import { Component } from '@angular/core';
@Component({
selector: 'app-article-editor',
template: `
<input [(ngModel)]="title">
<input [(ngModel)]="title">
<h2>{{ title }}</h2>
`
})
export class ArticleEditorComponent {
title = '';
}
Flesh it out:
import { Component } from '@angular/core';
@Component({
selector: 'app-article-editor',
template: `
<input [ngModel]="title" (ngModelChange)="title=$event">
<input [ngModel]="title" (ngModelChange)="title=$event">
<h2>{{ title }}</h2>
`
})
export class ArticleEditorComponent {
title = 'Angular conquer the world!';
}
Core bindings of ngModel essentially doing this:
import { Component } from '@angular/core';
@Component({
selector: 'app-article-editor',
template: `
<input [value]="title" (input)="title=$event.target.value">
<input [value]="title" (input)="title=$event.target.value">
<h2>{{ title }}</h2>
`
})
export class ArticleEditorComponent {
title = 'Let\'s do this.';
}
FormControl basic validation:
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
@Component({
selector: 'app-article-editor',
template: `
<p>Technology Name (required):</p>
<input [formControl]="nameControl" required>
<button (click)="submitName()">Save</button>
<h2>{{ name }}</h2>
`
})
export class ArticleEditorComponent {
name = '';
nameControl: FormControl = new FormControl();
submitName(): void {
if (this.nameControl.valid) {
this.name = this.nameControl.value;
} else {
alert('You are required');
}
}
}
Validators:
import { Component } from '@angular/core';
import { FormControl, Validators } from '@angular/forms';
@Component({
selector: 'app-article-editor',
template: `
<p>Technology Name (required):</p>
<input [formControl]="nameControl">
<button (click)="submitName()">Save</button>
<h2>{{ name }}</h2>
`
})
export class ArticleEditorComponent {
name = '';
nameControl: FormControl = new FormControl(null, Validators.required);
submitName(): void {
if (this.nameControl.valid) {
this.name = this.nameControl.value;
} else {
alert('You are required');
}
}
}
FormGroup:
import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
@Component({
selector: 'app-article-editor',
template: `
<p>Title: <input [formControl]="titleControl"></p>
<p>Text: <input [formControl]="textControl"></p>
<p><button (click)="saveArticle()">Save</button></p>
<hr>
<p>Preview:</p>
<div style="border: 1px solid #999; margin: 50px;">
<h1>{{ article.title }}</h1>
<p>{{ article.text }}</p>
</div>
`
})
export class ArticleEditorComponent {
article: { title: string, text: string } = {title: '', text: ''};
titleControl: FormControl = new FormControl(null, Validators.required);
textControl: FormControl = new FormControl(null, Validators.required);
articleFormGroup: FormGroup = new FormGroup({
title: this.titleControl,
text: this.textControl
});
saveArticle(): void {
if (this.articleFormGroup.valid) {
this.article = this.articleFormGroup.value;
} else {
console.log('Missing field(s)!');
}
}
}
FormArray:
import { Component } from '@angular/core';
import { FormControl, FormArray, Validators } from '@angular/forms';
@Component({
selector: 'app-article-editor',
template: `
<p>Tags:</p>
<ul>
<li *ngFor="let tag of tagCtrls; let i = index;">
<input [formControl]="tag">
<button (click)="remove(i)">x</button>
</li>
</ul>
<p><button (click)="add()">+</button></p>
<p><button (click)="saveHer()">Save Her</button></p>
`
})
export class ArticleEditorComponent {
tagCtrls: FormControl[] = [];
tagFormArr: FormArray = new FormArray(this.tagCtrls);
add(): void {
this.tagFormArr.push(new FormControl(null, Validators.required));
}
remove(idx: number): void {
this.tagFormArr.removeAt(idx);
}
saveHer(): void {
if (this.tagFormArr.valid) {
console.log('valid!');
} else {
console.log('missing field(s)!');
}
}
}
NgForm basic forms:
ngForm ngModel:
import { Component } from '@angular/core';
@Component({
selector: 'app-article-editor',
template: `
<form #f="ngForm" (ngSubmit)="saveHer(f)">
<p><input ngModel name="title" placeholder="Article Title"></p>
<p><textarea ngModel name="text" placeholder="Article Text"></textarea></p>
<p><button type="submit">Save</button></p>
</form>
`
})
export class ArticleEditorComponent {
saveHer(f: any): void {
console.log(f);
}
}
ngModelGroup:
import { Component } from '@angular/core';
@Component({
selector: 'app-article-editor',
template: `
<form #f="ngForm" (ngSubmit)="saveHer(f)">
<div ngModelGroup="article">
<p><input ngModel name="title" placeholder="Article Title"></p>
<p><textarea ngModel name="text" placeholder="Article Text"></textarea></p>
</div>
<p><button type="submit">Save</button></p>
</form>
`
})
export class ArticleEditorComponent {
saveHer(f: any): void {
console.log(f);
}
}
FormBuilder && formControlName:
import { Component, Inject } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-article-editor',
template: `
<form [formGroup]="articleGroup" (ngSubmit)="saveHer()">
<div formGroupName="article">
<p><input formControlName="title" placeholder="Article Title"></p>
<p><textarea formControlName="text" placeholder="Article Text"></textarea></p>
</div>
<p><button type="submit">Save</button></p>
</form>
`
})
export class ArticleEditorComponent {
articleGroup: FormGroup;
constructor(@Inject(FormBuilder) formBuilder: FormBuilder) {
this.articleGroup = formBuilder.group({
article: formBuilder.group({
title: [null, Validators.required],
text: [null, Validators.required]
})
});
}
saveHer(): void {
console.log(this.articleGroup);
}
}
Custom Validator:
import { Component, Inject } from '@angular/core';
import { FormControl, Validators } from '@angular/forms';
@Component({
selector: 'app-article-editor',
template: `
<h2>What I want is to be N°1.</h2>
<p><textarea [formControl]="bodyCtrl" placeholder="Article Text"></textarea></p>
<p><button (click)="saveHer()">Save</button></p>
`
})
export class ArticleEditorComponent {
articleBody = '';
bodyCtrl: FormControl = new FormControl(null, [Validators.required, this.wordCtrlValidator]);
wordCtrlValidator(c: any): {[key: string]: any} {
if (c.value) {
const wordCt: number = (c.value.match(/\S+/g) || []).length;
return wordCt <=10 ? null : { 'maxwords': {'limit': 10, 'actual': wordCt }};
}
}
saveHer(): void {
this.bodyCtrl.valid ? console.log('valid!') : console.log('invalid!');
}
}
Refactor Validator into its own directive:
import { Directive } from '@angular/core';
import { Validator, FormControl, NG_VALIDATORS } from '@angular/forms';
@Directive({
selector: '[appMaxWordCountValidator]',
inputs: ['rawCount: appMaxWordCountValidator'],
providers: [{
provide: NG_VALIDATORS,
useExisting: MaxWordCountValidatorDirective,
multi: true
}]
})
export class MaxWordCountValidatorDirective implements Validator {
rawCount = '';
validate(c: FormControl): {[key: string]: any} {
const wordCt: number = ((c.value || '').match(/\S+/g) || []).length;
return wordCt <= this.maxCount ? null : { 'maxwords': {'limit': this.maxCount, 'actual': wordCt }};
}
get maxCount(): number {
return parseInt(this.rawCount, 10);
}
}
Custom asynchronous validator with promise:
import { FormControl, Validator } from '@angular/forms';
export class DelayValidator implements Validator {
static validate(c: any): Promise<{[key: string]: any}> {
if (c.pristine && !c.value) {
return new Promise;
}
if (!c.delayPromise) {
c.delayPromise = new Promise((resolve) => {
setTimeout(() => {
console.log('resolve');
resolve();
}, 3000);
});
}
return c.delayPromise;
}
}