Angular 笔记12(表单与用户输入——响应式表单、验证表单、动态表单构建)

响应式表单

响应式表单概述

响应式表单对表单状态的每一次变更都会返回一个新的状态,这样可以在变化时维护模型的整体性。响应式表单是围绕 Observable 流构建的,表单的输入和值都是通过这些输入值组成的流来提供的,它可以同步访问。

此篇只记录需要注意的知识点,官方文档(中文版)更详细

显示表单控件的值

  • 通过可观察对象 valueChanges,你可以在模板中使用 AsyncPipe 或在组件类中使用 subscribe() 方法来监听表单值的变化。
  • 使用 value 属性。它能让你获得当前值的一份快照。
<p>
  Value: {{ name.value }}
</p>

更新部分数据模型

有两种更新模型值的方式:

  • 使用 setValue() 方法来为单个控件设置新值。 setValue() 方法会严格遵循表单组的结构,并整体性替换控件的值。
updateName() {
  this.name.setValue('Nancy');
}
  • 使用 patchValue() 方法可以用对象中所定义的任何属性为表单模型进行替换。
updateProfile() {
  this.profileForm.patchValue({
    firstName: 'Nancy',
    address: {
      street: '123 Drew Street'
    }
  });
}

使用 FormBuilder 服务生成控件

  • FormBuilder 是一个可注入的服务提供者,它是由 ReactiveFormModule 提供的。只要把它添加到组件的构造函数中就可以注入这个依赖。
  • FormBuilder 服务有三个方法:control()、group() 和 array()。这些方法都是工厂方法,用于在组件类中分别生成 FormControl、FormGroup 和 FormArray。
import { Component } from '@angular/core';
import { FormBuilder } from '@angular/forms';

@Component({
  selector: 'app-profile-editor',
  templateUrl: './profile-editor.component.html',
  styleUrls: ['./profile-editor.component.css']
})
export class ProfileEditorComponent {
  profileForm = this.fb.group({
    firstName: [''],
    lastName: [''],
    address: this.fb.group({
      street: [''],
      city: [''],
      state: [''],
      zip: ['']
    }),
  });

  constructor(private fb: FormBuilder) { }
}

显示表单状态

可以通过该 FormGroup 实例的 status 属性来访问其当前状态。

验证表单输入

此篇只记录需要注意的知识点,官方文档(中文版)更详细

在模板驱动表单中验证输入

<input id="name" name="name" class="form-control"
      required minlength="4" appForbiddenName="bob"
      [(ngModel)]="hero.name" #name="ngModel" >

<div *ngIf="name.invalid && (name.dirty || name.touched)"
    class="alert alert-danger">

  <div *ngIf="name.errors.required">
    Name is required.
  </div>
  <div *ngIf="name.errors.minlength">
    Name must be at least 4 characters long.
  </div>
  <div *ngIf="name.errors.forbiddenName">
    Name cannot be Bob.
  </div>

</div>
  • <input> 元素带有一些 HTML 验证属性:required 和 minlength。它还带有一个自定义的验证器指令 forbiddenName。
  • #name=“ngModel” 把 NgModel 导出成了一个名叫 name 的局部变量。NgModel 把自己控制的 FormControl 实例的属性映射出去,让你能在模板中检查控件的状态,比如 valid 和 dirty。
  • <div> 元素的 *ngIf 展示了一组嵌套的消息 div,但是只在有“name”错误和控制器为 dirty 或者 touched 时才出现。
  • 当用户在被监视的字段中修改该值时,控件就会被标记为 dirty(脏)。
  • 当用户的表单控件失去焦点时,该控件就会被标记为 touched(已接触)。

在响应式表单中验证输入

验证器(Validator)函数

  • 同步验证器:这些同步函数接受一个控件实例,然后返回一组验证错误或 null。你可以在实例化一个 FormControl 时把它作为构造函数的第二个参数传进去。
  • 异步验证器 :这些异步函数接受一个控件实例并返回一个 Promise 或 Observable,它稍后会发出一组验证错误或 null。在实例化 FormControl 时,可以把它们作为第三个参数传入。

出于性能方面的考虑,只有在所有同步验证器都通过之后,Angular 才会运行异步验证器。当每一个异步验证器都执行完之后,才会设置这些验证错误。

内置验证器函数

ngOnInit(): void {
  this.heroForm = new FormGroup({
    'name': new FormControl(this.hero.name, [
      Validators.required,
      Validators.minLength(4),
      forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator.
    ]),
    'alterEgo': new FormControl(this.hero.alterEgo),
    'power': new FormControl(this.hero.power, Validators.required)
  });
}

两个内置验证器 - Validators.required 和 Validators.minLength(4)

定义自定义验证器函数

/** A hero's name can't match the given regular expression */
export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
  return (control: AbstractControl): {[key: string]: any} | null => {
    const forbidden = nameRe.test(control.value);
    return forbidden ? {forbiddenName: {value: control.value}} : null;
  };
}

把自定义验证器添加到响应式表单中

this.heroForm = new FormGroup({
  name: new FormControl(this.hero.name, [
    Validators.required,
    Validators.minLength(4),
    forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator.
  ]),
  alterEgo: new FormControl(this.hero.alterEgo),
  power: new FormControl(this.hero.power, Validators.required)
});

为模板驱动表单中添加自定义验证器

<input id="name" name="name" class="form-control"
      required minlength="4" appForbiddenName="bob"
      [(ngModel)]="hero.name" #name="ngModel" >
// forbidden-name.directive.ts
@Directive({
  selector: '[appForbiddenName]',
  providers: [{provide: NG_VALIDATORS, useExisting: ForbiddenValidatorDirective, multi: true}]
})
export class ForbiddenValidatorDirective implements Validator {
  @Input('appForbiddenName') forbiddenName: string;

  validate(control: AbstractControl): {[key: string]: any} | null {
    return this.forbiddenName ? forbiddenNameValidator(new RegExp(this.forbiddenName, 'i'))(control)
                              : null;
  }
}

跨字段交叉验证

可能有一个提供互不兼容选项的表单,以便让用户选择 A 或 B,而不能两者都选。某些字段值也可能依赖于其它值;用户可能只有当选择了 A 之后才能选择 B。此时就需要跨字段交叉验证。

为响应式表单添加交叉验证

const heroForm = new FormGroup({
  'name': new FormControl(),
  'alterEgo': new FormControl(),
  'power': new FormControl()
}, { validators: identityRevealedValidator });
/** A hero's name can't match the hero's alter ego */
export const identityRevealedValidator: ValidatorFn = (control: FormGroup): ValidationErrors | null => {
  const name = control.get('name');
  const alterEgo = control.get('alterEgo');

  return name && alterEgo && name.value === alterEgo.value ? { identityRevealed: true } : null;
};

为模板驱动表单添加交叉验证

跨字段交叉验证器必须注册在表单的最高层

<form #heroForm="ngForm" appIdentityRevealed>
@Directive({
  selector: '[appIdentityRevealed]',
  providers: [{ provide: NG_VALIDATORS, useExisting: IdentityRevealedValidatorDirective, multi: true }]
})
export class IdentityRevealedValidatorDirective implements Validator {
  validate(control: AbstractControl): ValidationErrors {
    return identityRevealedValidator(control)
  }
}

创建异步验证器

实现自定义异步验证器

异步验证在同步验证完成后才会发生,并且只有在同步验证成功时才会执行。如果更基本的验证方法已经发现了无效输入,那么这种检查顺序就可以让表单避免使用昂贵的异步验证流程(例如 HTTP 请求)。

@Injectable({ providedIn: 'root' })
export class UniqueAlterEgoValidator implements AsyncValidator {
  constructor(private heroesService: HeroesService) {}

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

与任何验证器一样,如果表单有效,该方法返回 null,如果无效,则返回 ValidationErrors。

优化异步验证器的性能

异步验证器通常会执行某种 HTTP 请求来验证控件。每次击键后调度一次 HTTP 请求都会给后端 API 带来压力,应该尽可能避免。
使用模板驱动表单时,可以在模板中设置该属性。

<input [(ngModel)]="name" [ngModelOptions]="{updateOn: 'blur'}">

使用响应式表单时,可以在 FormControl 实例中设置该属性。

new FormControl('', {updateOn: 'blur'});

构建动态表单

许多表单(例如问卷)可能在格式和意图上都非常相似。为了更快更轻松地生成这种表单的不同版本,你可以根据描述业务对象模型的元数据来创建动态表单模板。然后就可以根据数据模型中的变化,使用该模板自动生成新的表单。

官方文档(中文版)介绍了一个问卷的动态表单,可以参考。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值