11 Form
在 Angular 中,表单有两种类型,分别为Template-Driven Forms(模板驱动表单)和Reactive Forms(响应式表单,或称模型驱动表单).
11.1 Template-Driven
11.1.1 Overview
表单的控制逻辑写在组件模板中,适合简单的表单类型。
11.1.2 快速上手
- 引入依赖模块 FormsModule
import { FormsModule } from "@angular/forms"
@NgModule({
imports: [FormsModule],
})
export class AppModule {}
- 将 DOM 表单转换为 ngForm
- 声明表单字段为 ngModel
<form #f="ngForm" (submit)="onSubmit(f)">
<input type="text" name="username" ngModel/>
<button>submit</button>
</form>
- 获取表单字段值
import { NgForm } from "@angular/forms"
export class AppComponent 1
onSubmit(form: NgForm) {
console.log(form.value)
}
}
- 表单分组
<form #f="ngForm" (submit)="onSubmit (f)">
<div ngModelGroup="user">
<input type="text" name="username" ngModel />
</div>
<div ngModelGroup="contact">
<input type="text" name="phone" ngModel />
</div>
<button>submit</button>
</form>
11.1.3 Validation in Template-Driven Forms
- required 必填字段
- minlength 字段最小长度
- maxlength 字段最大长度
- pattern 验证正则 例如:pattern="\d”匹配一个数值
<form #f="ngForm" (submit)="onSubmit(f)">
<input type="text" name="username" ngModel required pattern="\d" />
<button>提交</button>
</form>
在组件模板中显示表单项未通过时的错误信息。
<form #f="ngForm" (submit)="onSubmit(f)">
<input #username="ngModel" />
<div *ngIf="username.touched && !username valid && username.errors">
<div *ngIf="username.errors.required">请填写用户名</div>
<div *ngif="username.errors.pattern">不符合正则规则</div>
</div>
</form>
指定表单项未通过验证时的样式。
11.2 Reactive
11.2.1 Overview
表单的控制逻辑写在组件类中,对验证逻辑拥有更多的控制权,适合复杂的表单的类型。
在模型驱动表单中,表单字段需要是 FormControl 类的实例,实例对象可以验证表单字段中的值,值是否被修改过等等
一组表单字段构成整个表单,整个表单需要是 FormGroup 类的实例,它可以对表单进行整体验证。
- FormControl:表单组中的一个表单项
- FormGroup:表单组,表单至少是一个 FormGroup
- FormArray:用于复杂表单,可以动态添加表单项或表单组,在表单验证时,FormArray 中有一项没通过,整体没通过。
11.2.2 Basic Usage of Reactive Forms
- 引入 ReactiveFormsModule
import { ReactiveFormsModule } from "@angular/forms"
@NgModule ({
imports: [ReactiveFormsModule]
})
export class AppModule {}
- 在组件类中创建 FormGroup 表单控制对象
import { FormControl, FormGroup } from "@angular/forms"
export class AppComponent {
contactForm: FormGroup = new FormGroup ( {
name: new FormControl(),
phone: new FormControl()
})
}
- 关联组件模板中的表单
<form [formGroup]="contactForm" (submit)="onSubmit ()">
<input type="text" formControlName="name" />
<input type="text" formControlName="phone" />
<button>
submit
</button>
</form>
- 获取表单值
- 设置表单default value
export class AppComponent {
contactForm: FormGroup = new FormGroup({
username: new FormControl("I am a default value"),
phone: new FormControl(),
})
onSubmit() {
console.log(this.contactForm.value)
}
}
- 表单分组
src/app/components/form/reactive/reactive-form-control/reactive-form-control.component.ts
:
import { Component, OnInit } from '@angular/core';
import { Form, FormControl, FormGroup } from '@angular/forms';
@Component({
selector: 'app-reactive-form-control',
templateUrl: './reactive-form-control.component.html',
styleUrls: ['./reactive-form-control.component.css']
})
export class ReactiveFormControlComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
// contactForm: FormGroup = new FormGroup({
// username: new FormControl("I am a default value"),
// phone: new FormControl(),
// })
contactForm: FormGroup = new FormGroup({
fullName: new FormGroup({
firstName: new FormControl("I am a default value"),
lastName: new FormControl(),
}),
phone: new FormControl(),
})
onSubmit() {
// console.log(this.contactForm.value)
console.log(this.contactForm.value);
console.log(this.contactForm.value.fullName.firstName);
console.log(this.contactForm.get(["fullName", "lastName"])?.value);
}
}
src/app/components/form/reactive/reactive-form-control/reactive-form-control.component.html
:
<p>reactive-form-control works!</p>
<!-- <form [formGroup]="contactForm" (submit)="onSubmit()">
<input type="text" name="username" formControlName="username" />
<input type="text" name="phone" formControlName="phone" />
<button>submit</button>
</form> -->
<form [formGroup]="contactForm" (submit)="onSubmit()">
<div formGroupName="fullName">
<input type="text" name="firstName" formControlName="firstName" />
<input type="text" name="lastName" formControlName="lastName" />
</div>
<input type="text" name="phone" formControlName="phone" />
<button>submit</button>
</form>
11.2.3 FormArray
使用场景: 当你在找工作的时候, 在招聘网站中填写简历, 其中有一项就叫做项目经验, 由于项目经验这个东西, 有的人比较多, 有的人比较少. 所以不知道在一上来的时候需要放置多少组项目经验. 默认中一般都会放置一组项目经验. 然后在下面会添加一个按钮, 就会多出一组项目经验的填写框.
需求: 在页面中默认显示一组联系方式, 通过点击按钮可以添加更多联系方式组.
src/app/components/form/reactive/reactive-form-array/reactive-form-array.component.ts
:
import { Component, OnInit } from '@angular/core';
import { FormArray, FormControl, FormGroup } from '@angular/forms';
@Component({
selector: 'app-reactive-form-array',
templateUrl: './reactive-form-array.component.html',
styleUrls: ['./reactive-form-array.component.css']
})
export class ReactiveFormArrayComponent implements OnInit {
constructor() { }
ngOnInit(): void {
this.addContact(); // 添加默认的联系方式
}
// 表单
contactForm: FormGroup = new FormGroup({
contacts: new FormArray([])
})
// get是一个属性访问器
get contacts() {
return this.contactForm.get("contacts") as FormArray;
}
// '' vs "" 使用有什么区别 在Angular中 没有区别
addContact() {
let myContacts: FormGroup = new FormGroup({
address: new FormControl(),
phone: new FormControl(),
name: new FormControl(),
});
this.contacts.push(myContacts); // 向联系方式数组中添加联系方式
}
removeContact(index: number) {
this.contacts.removeAt(index);
}
onSubmit() {
console.log(this.contactForm.value);
}
}
src/app/components/form/reactive/reactive-form-array/reactive-form-array.component.html
:
<p>reactive-form-array works!</p>
<form [formGroup]="contactForm" (submit)="onSubmit()">
<div formArrayName="contacts">
<div *ngFor="let contact of contacts.controls; let i = index" [formGroupName]="i">
<input type="text" formControlName="name" />
<input type="text" formControlName="address" />
<input type="text" formControlName="phone" />
<button (click)="removeContact(i)">Remove</button>
</div>
</div>
<button (click)="addContact()">Add Contact</button>
<button>submit</button>
</form>
11.2.4 Built-in Validation in Reactive Forms
- 使用内置验证器提供的验证规则验证表单字段
import { Component, OnInit } from '@angular/core';
import { Form, FormControl, FormGroup, Validators } from '@angular/forms';
contactForm: FormGroup = new FormGroup({
name: new FormControl("", [
Validators.required,
Validators.minLength(5),
]),
})
- 获取整体表单是否验证通过
- 在组件模板中显示为验证通过时的错误信息
<p>reactive-validation works!</p>
<form [formGroup]="contactForm" (submit)="onSubmit()">
<input type="text" formControlName="name" />
<div *ngIf="name?.touched && name?.invalid && name?.errors">
<div *ngIf="name?.errors?.['required']">name is required</div>
<div *ngIf="name?.errors?.['minlength']">name is too short</div>
<div *ngIf="name?.errors?.['maxlength']">name is too long</div>
</div>
<button [disabled]="contactForm.invalid">submit</button>
<!-- <button>submit</button> -->
</form>
src/app/components/form/reactive/reactive-validation/reactive-validation.component.ts
:
import { Component, OnInit } from '@angular/core';
import { Form, FormControl, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-reactive-validation',
templateUrl: './reactive-validation.component.html',
styleUrls: ['./reactive-validation.component.css']
})
export class ReactiveValidationComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
contactForm: FormGroup = new FormGroup({
name: new FormControl("", [
Validators.required,
Validators.minLength(5),
]),
})
onSubmit() {
console.log(this.contactForm.valid);
}
get name() {
return this.contactForm.get("name");
}
}
src/app/components/form/reactive/reactive-validation/reactive-validation.component.html
:
<p>reactive-validation works!</p>
<form [formGroup]="contactForm" (submit)="onSubmit()">
<input type="text" formControlName="name" />
<div *ngIf="name?.touched && name?.invalid && name?.errors">
<div *ngIf="name?.errors?.['required']">name is required</div>
<div *ngIf="name?.errors?.['minlength']">name is too short</div>
<div *ngIf="name?.errors?.['maxlength']">name is too long</div>
</div>
<button [disabled]="contactForm.invalid">submit</button>
<!-- <button>submit</button> -->
</form>
11.2.5 Synchronous Validation in Reactive Forms
- 自定义验证器的类型是 TypeScript类
- 类中包含具体的验证方法,验证方法必须为静态方法
- 验证方法有一个参数control,类型为 AbstractControl。其实就是 FormControl 类的实例对象的类型
- 如果验证成功,返回 null
- 如果验证失败,返回对象,对象中的属性即为验证标识,值为true,标识该项验证失败
- 验证方法的返回值为 ValidationErrors | null
src/app/components/form/reactive/reactive-validation/myValidators.ts
:
import { AbstractControl, ValidationErrors } from "@angular/forms";
export class MyValidators {
static cannotContainSpace(control: AbstractControl): ValidationErrors | null {
if (/\s/.test(control.value)) {
return {
cannotContainSpace: true
};
}
return null;
}
}
src/app/components/form/reactive/reactive-validation/reactive-validation.component.ts
:
import { Component, OnInit } from '@angular/core';
import { Form, FormControl, FormGroup, Validators } from '@angular/forms';
import { MyValidators } from './myValidators';
@Component({
selector: 'app-reactive-validation',
templateUrl: './reactive-validation.component.html',
styleUrls: ['./reactive-validation.component.css']
})
export class ReactiveValidationComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
contactForm: FormGroup = new FormGroup({
name: new FormControl("", [
Validators.required,
Validators.minLength(5),
MyValidators.cannotContainSpace // custom validator
]),
})
onSubmit() {
console.log(this.contactForm.valid);
}
get name() {
return this.contactForm.get("name");
}
}
src/app/components/form/reactive/reactive-validation/reactive-validation.component.html
:
<p>reactive-validation works!</p>
<form [formGroup]="contactForm" (submit)="onSubmit()">
<input type="text" formControlName="name" />
<div *ngIf="name?.touched && name?.invalid && name?.errors">
<div *ngIf="name?.errors?.['required']">name is required</div>
<div *ngIf="name?.errors?.['minlength']">name is too short</div>
<div *ngIf="name?.errors?.['maxlength']">name is too long</div>
<div *ngIf="name?.errors?.['cannotContainSpace']">name cannot contain space</div>
</div>
<button [disabled]="contactForm.invalid">submit</button>
<!-- <button>submit</button> -->
</form>
11.2.6 Asynchronous Validation in Reactive Forms
src/app/components/form/reactive/reactive-validation/myValidators.ts
:
import { AbstractControl, ValidationErrors } from "@angular/forms";
export class MyValidators {
static cannotContainSpace(control: AbstractControl): ValidationErrors | null {
if (/\s/.test(control.value)) {
return {
cannotContainSpace: true
};
}
return null;
}
// async validator
// promise 是异步的
static shouldBeUnique(control: AbstractControl): Promise<ValidationErrors | null> {
return new Promise(function (resolve) {
setTimeout(function () {
if (control.value === "admin") {
resolve({
shouldBeUnique: true
})
} else {
resolve(null)
}
}, 2000)
})
}
}
// promise是什么?
// promise 是异步的
// promise 是一个对象
// promise 是一个对象, 这个对象代表了一个异步操作的最终完成(或失败)及其结果值的表示。
// promise 有三种状态: pending, fulfilled, rejected
// promise 有两个方法: resolve, reject
// promise 有一个属性: then
// resolve 和 reject 是两个函数, 这两个函数是由 promise 的构造函数传入的
// promise 的 then 方法接受两个函数作为参数, 分别是成功回调和失败回调
// promise 的 then 方法返回一个新的 promise 对象
// resolve 是一个函数, 这个函数接受一个参数, 这个参数就是 promise 的结果值
src/app/components/form/reactive/reactive-validation/reactive-validation.component.ts
:
import { Component, OnInit } from '@angular/core';
import { Form, FormControl, FormGroup, Validators } from '@angular/forms';
import { MyValidators } from './myValidators';
@Component({
selector: 'app-reactive-validation',
templateUrl: './reactive-validation.component.html',
styleUrls: ['./reactive-validation.component.css']
})
export class ReactiveValidationComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
contactForm: FormGroup = new FormGroup({
name: new FormControl("", [ // 数组当中写的都是同步的验证器
Validators.required,
Validators.minLength(5),
MyValidators.cannotContainSpace // custom validator
], MyValidators.shouldBeUnique), // 异步验证器
})
onSubmit() {
console.log(this.contactForm.valid);
}
get name() {
return this.contactForm.get("name");
}
}
src/app/components/form/reactive/reactive-validation/reactive-validation.component.html
:
<p>reactive-validation works!</p>
<form [formGroup]="contactForm" (submit)="onSubmit()">
<input type="text" formControlName="name" />
<div *ngIf="name?.touched && name?.invalid && name?.errors">
<div *ngIf="name?.errors?.['required']">name is required</div>
<div *ngIf="name?.errors?.['minlength']">name is too short</div>
<div *ngIf="name?.errors?.['maxlength']">name is too long</div>
<div *ngIf="name?.errors?.['cannotContainSpace']">name cannot contain space</div>
<div *ngIf="name?.errors?.['shouldBeUnique']">name should be unique</div>
</div>
<div *ngIf="name?.pending">validating...</div>
<button [disabled]="contactForm.invalid">submit</button>
<!-- <button>submit</button> -->
</form>
11.2.7 FormBuilder
创建表单的快捷方式。
-
this.fb.control
:表单项 this.tb.group
:表单组,表单至少是一个 FormGroup-
this.tb.array
:用于复杂表单,可以动态添加表单项或表单组,在表单验证时,FormArray 中有一项没通过,整体没通过。
import ( FormBuilder, FormGroup, Validators } from "@angular/forms"
export class AppComponent {
contactForm: FormGroup
constructor (private fb: FormBuilder {
this.contactForm = this.fb.group({
fullName: this.fb. group({
firstName: ["sam", [Validators.required]],
lastName: [""]
}),
phone: []
})
}
}
src/app/components/form/reactive/reactive-form-builder/reactive-form-builder.component.ts
:
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { first, last } from 'rxjs';
@Component({
selector: 'app-reactive-form-builder',
templateUrl: './reactive-form-builder.component.html',
styleUrls: ['./reactive-form-builder.component.css']
})
export class ReactiveFormBuilderComponent implements OnInit {
contactForm: FormGroup
constructor(private fb: FormBuilder) {
this.contactForm = this.fb.group({
fullName: this.fb.group({
firstName: ["default", [Validators.required]],
lastName: []
})
})
}
ngOnInit(): void {
}
onSubmit() {
console.log(this.contactForm.value);
}
}
src/app/components/form/reactive/reactive-form-builder/reactive-form-builder.component.html
:
<p>reactive-form-builder works!</p>
<form [formGroup]="contactForm" (submit)="onSubmit()">
<div formGroupName="fullName">
<input type="text" name="firstName" formControlName="firstName" />
<input type="text" name="lastName" formControlName="lastName" />
</div>
<button>submit</button>
</form>
11.2.8 Practice Creating Forms with FormBuilder
- checkbox: 获取一组复选框中选中的值
src/app/components/form/practice/checkbox/checkbox.component.ts
:
import { Component, OnInit } from '@angular/core';
import { Form, FormArray, FormBuilder, FormGroup } from '@angular/forms';
interface Data {
name: string
value: string
}
@Component({
selector: 'app-checkbox',
templateUrl: './checkbox.component.html',
styleUrls: ['./checkbox.component.css']
})
export class CheckboxComponent implements OnInit {
ngOnInit(): void {
}
Data: Array<Data> = [
{
name: 'Angular',
value: 'angular'
},
{
name: 'React',
value: 'react'
},
{
name: 'Vue',
value: 'vue'
},
{
name: 'Svelte',
value: 'svelte'
}
]
form: FormGroup
constructor(private fb: FormBuilder) {
this.form = this.fb.group({
checkArray: this.fb.array([])
})
}
onSubmit() {
console.log(this.form.value);
}
onChange(evnet: Event) {
const target = evnet.target as HTMLInputElement;
const value = target.value;
const checked = target.checked;
const checkArray = this.form.get('checkArray') as FormArray;
if (checked) {
checkArray.push(this.fb.control(value));
} else {
const index = checkArray.controls.findIndex(control => control.value === value)
checkArray.removeAt(index);
}
console.log(checkArray.controls);
}
}
src/app/components/form/practice/checkbox/checkbox.component.html
:
<p>checkbox works!</p>
<form [formGroup]="form" (submit)="onSubmit()">
<label *ngFor="let item of Data">
<input type="checkbox" [value]="item.value" (change)="onChange($event)" /> {{ item.name }}
</label>
<button>submit</button>
</form>
- radio button:
src/app/components/form/practice/radiobutton/radiobutton.component.ts
:
import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
@Component({
selector: 'app-radiobutton',
templateUrl: './radiobutton.component.html',
styleUrls: ['./radiobutton.component.css']
})
export class RadiobuttonComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
form: FormGroup = new FormGroup({
gender: new FormControl()
});
onSubmit() {
console.log(this.form.value)
}
}
src/app/components/form/practice/radiobutton/radiobutton.component.html
:
<p>radiobutton works!</p>
<form [formGroup]="form" (submit)="onSubmit()">
<input type="radio" value="male" formControlName="gender" /> male
<input type="radio" value="female" formControlName="gender" /> female
<button>submit</button>
</form>
11.2.9 Other
- patchValue:设置表单控件的值(可以设置全部,也可以设置其中某一个,其他不受影响)
- setValue:设置表单控件的值(设置全部,不能排除任何一个)
- valueChanges: 当表单控件的值发生变化时被触发的事件
- reset:表单内容置空
src/app/components/form/other/other.component.ts
import { Component, OnInit } from '@angular/core';
import { Form, FormControl, FormGroup } from '@angular/forms';
import { first } from 'rxjs';
@Component({
selector: 'app-other',
templateUrl: './other.component.html',
styleUrls: ['./other.component.css']
})
export class OtherComponent implements OnInit {
constructor() { }
ngOnInit(): void {
this.form.get('lastName')?.valueChanges.subscribe((value) => {
console.log(value);
})
}
form: FormGroup = new FormGroup({
firstName: new FormControl(),
lastName: new FormControl(),
});
onSubmit() {
console.log(this.form.value)
}
onPatchValue() {
this.form.patchValue({
firstName: "John",
})
}
onSetValue() {
this.form.setValue({
firstName: "John2",
lastName: "Doe",
})
}
onReset() {
this.form.reset();
}
}
src/app/components/form/other/other.component.html
:
<p>other works!</p>
<form [formGroup]="form" (submit)="onSubmit()">
<input type="text" formControlName="firstName" />
<input type="text" formControlName="lastName" />
<button (click)="onPatchValue()">patchValue</button>
<button (click)="onSetValue()">setValue</button>
<button (click)="onReset()">reset</button>
<button>submit</button>
</form>