Angular 表单介绍之响应式表单

373 阅读3分钟

angular 表单介绍之响应式表单

响应式表单

响应式表单与模板驱动表单有着显著的不同点。响应式表单通过对数据模型的同步访问提供了更多的可预测性,使用 Observable 的操作符提供了不可变性,并且通过 Observable 流提供了变化追踪功能。

模板驱动表单允许你直接在模板中修改数据,但不像响应式表单那么明确,因为它们依赖嵌入到模板中的指令,并借助可变数据来异步跟踪变化。

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

基础类

详情
AbstractControl所有三种表单控件类(FormControl、FormGroup 和 FormArray)的抽象基类。它提供了一些公共的行为和属性。
FormControl管理单体表单控件的值和有效性状态。它对应于 HTML 的表单控件,比如 或 。
FormGroup管理一组 AbstractControl 实例的值和有效性状态。该组的属性中包括了它的子控件。组件中的顶层表单就是 FormGroup。
FormArray管理一些 AbstractControl 实例数组的值和有效性状态。
FormBuilder一个可注入的服务,提供一些用于提供创建控件实例的工厂方法。
FormRecord跟踪 FormControl 实例集合的值和有效性状态,每个实例都具有相同的值类型。

快速开始

准备

在对应组件module中引入ReactiveFormsModule

  • 若使用响应式表单,则导入 ReactiveFormsModule
  • 若使用模板驱动式表单,则导入 FormsModule
在组件类中创建 FormGroup 表单控制对象
form: FormGroup  
constructor(private fb: FormBuilder, private messageService: MessageService) {
    this.form = this.fb.group({
      name: ['', [Validators.pattern('^[a-zA-Z][a-zA-Z0-9]{3,15}$')]],
      sex: ['', [Validators.required]],
      birthDate: ['', [Validators.required]],
      educationLevel: ['', [Validators.required]],
      tel: [null, [Validators.pattern('^1(3|4|5|7|8)\d{9}$')]],
      confirmTel: ['', [Validators.required, this.confirmationValidator]],
      contacts: new FormArray([
        this.fb.group({
          username: new FormControl(),
          address: new FormControl(),
          phone: new FormControl()
        })
      ])
    })
  }
组件模板创建表单

<form [formGroup]="form"></form>

添加表单控件
<div class="grid">
  <div class="col-6 col-offset-2">
    <h4>响应式表单</h4>
​
    <form [formGroup]="form">
      <div class="card p-fluid">
        <div class="field grid">
          <label
            htmlFor="name"
            class="col-12 mb-3 md:col-3 md:mb-0 text-xl justify-content-end"
            >姓名:<span style="color: firebrick">*</span></label
          >
          <div class="col-12 md:col-9">
            <input
              pInputText
              id="name"
              type="text"
              name="name"
              placeholder="真实姓名"
              formControlName="name"
            />
​
            <!-- *ngIf="validateForm.get('tel')?.dirty && validateForm.get('tel')?.errors" -->
            <small
              [class]="{
                'form-prompt': true,
                'form-error':
                  form.get('name')?.dirty && form.get('name')?.errors
              }"
            >
              请输入以字母开头,3到15位名称
            </small>
          </div>
        </div>
        <div class="field grid">
          <label
            htmlFor="tel"
            class="col-12 mb-3 md:col-3 md:mb-0 text-xl justify-content-end"
            >电话:<span style="color: firebrick">*</span></label
          >
          <div class="col-12 md:col-9">
            <input
              pInputText
              id="tel"
              type="text"
              name="tel"
              placeholder="电话"
              formControlName="tel"
            />
          </div>
        </div>
        <div class="field grid">
          <label
            htmlFor="confirmTel"
            class="col-12 mb-3 md:col-3 md:mb-0 text-xl justify-content-end"
            >确认电话:<span style="color: firebrick">*</span></label
          >
          <div class="col-12 md:col-9">
            <input
              pInputText
              id="confirmTel"
              type="text"
              name="confirmTel"
              placeholder="电话"
              formControlName="confirmTel"
            />
          </div>
        </div>
        <div class="field grid">
          <label
            htmlFor="sex"
            class="col-12 mb-3 md:col-3 md:mb-0 text-xl justify-content-end"
            >联系方式:<span style="color: firebrick">*</span></label
          >
          <div class="col-12 md:col-9" formArrayName="contacts">
            <div
              class="formgrid grid"
              *ngFor="let contact of contacts.controls; let i = index"
              [formGroupName]="i"
            >
              <div class="field col-4">
                <input
                  pInputText
                  type="text"
                  name="username"
                  placeholder="用户"
                  formControlName="username"
                  class=""
                />
              </div>
              <div class="field col-4">
                <input
                  pInputText
                  type="text"
                  name="address"
                  placeholder="地址"
                  formControlName="address"
                />
              </div>
​
              <div class="field col-3">
                <input
                  pInputText
                  type="text"
                  name="phone"
                  placeholder="电话"
                  formControlName="phone"
                />
              </div>
              <div class="field col-1">
                <button type="button" [disabled]="i==0" pButton pRipple class="p-button-text p-button-sm pl-0" (click)="removeContact(i)"><i class="pi pi-times"></i></button>
              </div>
            </div>
            <div>
                <button type="button" pButton pRipple class="p-button-text p-button-sm" (click)="addContact()"><i class="pi pi-plus"></i></button>
            </div>
          </div>
        </div>
        ....
        <div class="field grid justify-content-center">
          <button type="submit" pButton pRipple>提交</button>
        </div>
      </div>
    </form>
  </div>
</div>
​

表单控件使用 formControlName="phone" 绑定表单值

添加表单校验

使用Validators类进行表单校验,在创建FormGroup添加表单规则

错误信息提示

使用如下获取控件状态 form.get('name')?.dirty && form.get('name')?.errors,其对应属性值来源于FormControl类,具体属性可参考

https://angular.cn/api/forms/FormControl

自定义验证器
confirmationValidator = (
    control: UntypedFormControl
  ): { [s: string]: boolean } => {
    if (!control.value) {
      return { required: true }
    } else if (control.value !== this.form.controls['tel'].value) {
      return { confirm: true, error: true }
    }
    return {}
  }

自定义异步验证器和同步验证器很像,只是它们必须返回一个稍后会输出 null 或“验证错误对象”的承诺(Promise)或可观察对象,如果是可观察对象,那么它必须在某个时间点被完成(complete),那时候这个表单就会使用它输出的最后一个值作为验证结果。(译注:HTTP 服务是自动完成的,但是某些自定义的可观察对象可能需要手动调用 complete 方法)

异步验证器
  • validate() 函数必须返回一个 Promise 或可观察对象

  • 回的可观察对象必须是有尽的,这意味着它必须在某个时刻完成(complete)。要把无尽的可观察对象转换成有尽的,可以在管道中加入过滤操作符,比如 firstlasttaketakeUntil

    示例

     validate(
        control: AbstractControl
      ): Observable<ValidationErrors | null> {
        return this.heroesService.isAlterEgoTaken(control.value).pipe(
          map(isTaken => (isTaken ? { uniqueAlterEgo: true } : null)),
          catchError(() => of(null))
        );
      }
    

表单控制

//可以使用ngModalChange
<input type="text" [(ngModal)]="name" (ngModalChange)="nameChange()" />
//也可以使用如下
ngOnInt() {
        this.form.get("name").valueChanges.subscribe(data => {
            console.log(data);
        }
    }

其他

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

源码地址