Skip to content

11 Form

在 Angular 中,表单有两种类型,分别为Template-Driven Forms(模板驱动表单)和Reactive Forms(响应式表单,或称模型驱动表单).

11.1 Template-Driven

11.1.1 Overview

表单的控制逻辑写在组件模板中,适合简单的表单类型。

11.1.2 快速上手

  1. 引入依赖模块 FormsModule
import { FormsModule } from "@angular/forms"
@NgModule({
 imports: [FormsModule],
})
export class AppModule {}
  1. 将 DOM 表单转换为 ngForm
<form #f="ngForm" (submit)="onSubmit(f)"></form>
  1. 声明表单字段为 ngModel
<form #f="ngForm" (submit)="onSubmit(f)">
 <input type="text" name="username" ngModel/>
 <button>submit</button>
</form>
  1. 获取表单字段值
import { NgForm } from "@angular/forms"
export class AppComponent 1
 onSubmit(form: NgForm) {
     console.log(form.value)
 }
}

Screenshot 2025-01-18 at 21.14.41

Screenshot 2025-01-18 at 21.15.38

  1. 表单分组
<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>

Screenshot 2025-01-18 at 22.14.39

Screenshot 2025-01-18 at 22.14.48

Screenshot 2025-01-18 at 22.15.21

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>
export class AppComponent {
    onSubmit (form: NgForm) {
        // 查看表单整体是否验证通过
    console.log(form.valid)
  }
}
<!-- 表单整体未通过验证时禁用提交表单 -->
<button type="submit" [disabled]="f.invalid">submit</button>

在组件模板中显示表单项未通过时的错误信息。

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

指定表单项未通过验证时的样式。

input.ng-touched.ng-invalid{
  border: 2px solid red;
}

Screenshot 2025-01-18 at 23.20.20

Screenshot 2025-01-18 at 23.19.57

Screenshot 2025-01-18 at 23.21.06

Screenshot 2025-01-18 at 23.21.16

Screenshot 2025-01-18 at 23.21.26

Screenshot 2025-01-18 at 23.27.16

Screenshot 2025-01-18 at 23.27.25

Screenshot 2025-01-18 at 23.31.56

Screenshot 2025-01-18 at 23.32.06

Screenshot 2025-01-18 at 23.36.07

Screenshot 2025-01-18 at 23.36.20

11.2 Reactive

11.2.1 Overview

表单的控制逻辑写在组件类中,对验证逻辑拥有更多的控制权,适合复杂的表单的类型。

在模型驱动表单中,表单字段需要是 FormControl 类的实例,实例对象可以验证表单字段中的值,值是否被修改过等等

Screenshot 2025-01-18 at 23.41.42

一组表单字段构成整个表单,整个表单需要是 FormGroup 类的实例,它可以对表单进行整体验证。

Screenshot 2025-01-18 at 23.46.04

  1. FormControl:表单组中的一个表单项
  2. FormGroup:表单组,表单至少是一个 FormGroup
  3. FormArray:用于复杂表单,可以动态添加表单项或表单组,在表单验证时,FormArray 中有一项没通过,整体没通过。

11.2.2 Basic Usage of Reactive Forms

  1. 引入 ReactiveFormsModule
import { ReactiveFormsModule } from "@angular/forms"

@NgModule ({
 imports: [ReactiveFormsModule]
})

export class AppModule {}
  1. 在组件类中创建 FormGroup 表单控制对象
import { FormControl,    FormGroup } from "@angular/forms"

export class AppComponent {
 contactForm: FormGroup = new FormGroup ( {
     name: new FormControl(), 
    phone: new FormControl()
 })
}
  1. 关联组件模板中的表单
<form [formGroup]="contactForm" (submit)="onSubmit ()">
 <input type="text" formControlName="name" />
 <input type="text" formControlName="phone" />
  <button>
    submit
  </button>
</form>
  1. 获取表单值
// 导出一个类 
export class AppComponent {
  onSubmit() {
    console.log(this.contactForm.value) 
  }
}
  1. 设置表单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) 
  }
}

Screenshot 2025-01-19 at 00.12.15

Screenshot 2025-01-19 at 00.12.26

Screenshot 2025-01-19 at 00.13.03

Screenshot 2025-01-19 at 00.13.08

  1. 表单分组

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>

Screenshot 2025-02-09 at 01.18.41

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>

Screenshot 2025-02-09 at 02.17.21

11.2.4 Built-in Validation in Reactive Forms

  1. 使用内置验证器提供的验证规则验证表单字段
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),
    ]),
  })
  1. 获取整体表单是否验证通过
  onSubmit() {
    console.log(this.contactForm.valid);
  }
  onSubmit() {
    console.log(this.contactForm.valid);
  }
<!-- 表单整体未验证通过时禁用表单按钮-->
  1. 在组件模板中显示为验证通过时的错误信息
      get name() {
        return this.contactForm.get("name");
      }
    
<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>

Screenshot 2025-02-09 at 12.00.18

Screenshot 2025-02-09 at 12.00.28

11.2.5 Synchronous Validation in Reactive Forms

  1. 自定义验证器的类型是 TypeScript类
  2. 类中包含具体的验证方法,验证方法必须为静态方法
  3. 验证方法有一个参数control,类型为 AbstractControl。其实就是 FormControl 类的实例对象的类型
  4. 如果验证成功,返回 null
  5. 如果验证失败,返回对象,对象中的属性即为验证标识,值为true,标识该项验证失败
  6. 验证方法的返回值为 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>

Screenshot 2025-02-09 at 12.56.52

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>

Screenshot 2025-02-09 at 13.07.31

Screenshot 2025-02-09 at 13.07.42

11.2.7 FormBuilder

创建表单的快捷方式。

  1. this.fb.control:表单项
  2. this.tb.group:表单组,表单至少是一个 FormGroup
  3. 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: []
            })
    }
}

Screenshot 2025-01-19 at 00.41.14

Screenshot 2025-01-19 at 00.41.22

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

  1. 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>

Screenshot 2025-02-09 at 14.04.34

  1. 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>

Screenshot 2025-02-09 at 14.10.58

11.2.9 Other

  1. patchValue:设置表单控件的值(可以设置全部,也可以设置其中某一个,其他不受影响)
  2. setValue:设置表单控件的值(设置全部,不能排除任何一个)
  3. valueChanges: 当表单控件的值发生变化时被触发的事件
  4. 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>

Screenshot 2025-02-09 at 15.45.16