所有表单对象_创建带有类型检查的Angular响应式表单

4d6660beff34f5212f86aba2155d7ba3.png

起因

最近在重构公司项目的表单部分,因为业务的数据流转都依靠着字段丰富的表格作为基础。因为是重构,所以编码工作也进行地更细心一点,考虑到是否便于以后维护,和是否有足够清晰的架构等。这其中一个一直无法解决的难点就是Angular自身提供的响应式表单(ReactiveForm)并没有提供类型确定的 value 值给用户,在项目的表单逻辑复杂的场景下(比如说有20几中不同的表单字段),常常让人感到很难受。

35d362a7e59c9d2eadb52c2865dbccaf.png

想象中

7a2c735c7b3b63ad440ab0826d86e99e.gif

790f78c733398f967238a33fbf00480a.gif

现实

大家都知道,使用TypeScript可以在编码时享受像强类型语言那样的类型提示。但是TypeScript中存在 any 类型,这种类型会导致变量的数据类型随意更改,编辑器也无法给出正确的智能提示(IntelliSense),这就很僵硬。

// 类型定义

我主要想解决的问题是 Angular 响应式表单中 FormGroup 对象的 value 属性返回的是 any 类型。比如说我们要创造一个带有两个表单控件的响应式表单,这两个表单控件的数据类型分别为: string Member 。下面通过代码来展示一下理想与现实的差距= =。

准备

// 一个自定义的 Member 类型
interface Member {
  name: string;
  uid: number;
}

// 创建出来的表单类型
interface IMyForm {
  dpt: string;
  member: Member;
}

// 一个 FormGroup 对象,有 `dpt` 和 `member` 两个字段  
myForm = this.formBuilder.group({
  dpt: '前端',
  member: { name: '小张', uid: 1 }
});

理想和现实的差距

// 理想
myForm.value;  // IMyForm 类型
// 现实
myForm.value;  // any 类型

// 理想 get方法中自动提示 'dpt' | 'member' 等应该出现的key值
myForm.get('member').value;
// 现实 (必须知道表单中有 `member` 属性)
myForm.get('member').value;

翻看Angular在github上的issue后得知,官方已经在跟进此feature的优化,可是我迫不及待地想解决这个问题,所以用了点小hack来解决此问题。

以下是官方实现进度的链接

  • Proposal - ReactiveForms: add AbstractControl.getChild<T> method
  • Reactive forms are not strongly typed

HACK一下

主要的解决思路是利用到了TypeScript中的泛型 ,和索引类型 等特性。

主要实现细节如下

FormGroupTypeSafe

将原生的FormGroup类型扩展成我们期望的带泛型的抽象类

export abstract class FormGroupTypeSafe<T> extends FormGroup {
  // 返回一个确定类型的value
  value: T;
  // 创建一个额外的getSafe方法,使用 keyof 索引查询方法,找到泛型中所可能出现的所有key值
  public abstract getSafe(p: keyof T): AbstractControl;
  public abstract setControlSafe(p: keyof T, control: AbstractControl): void;
}

FormBuilderTypeSafe

将原生的formBuilder扩展成支持泛型的service

export class FormBuilderTypeSafe extends FormBuilder {
   // 重写group方法使之返回的数据带有类型约束
   group<T>(controlsConfig: T, extra?: { [key: string]: any;} | null): FormGroupTypeSafe<T> {
      // 必须使用父类方法实例化group
      let gr = super.group(controlsConfig, extra) as FormGroupTypeSafe<T>;
      // 实现自己的抽象方法
      if (gr) {
        gr.getSafe = (p: keyof T): AbstractControl => {
          return gr.get(p as string) as FormGroupTypeSafe<T>;
        }

        gr.setControlSafe = (p: keyof T, control: AbstractControl): void => {
          gr.setControl(p as string, control);
        }
      }

     return gr;
   }
}

ok,到此为止所有的前置工作都准备完毕,接下来就是去组件中使用检测效果的时候啦~

// 一个自定义的 Member 类型
interface Member {
  name: string;
  uid: number;
}

// 创建出来的表单类型
interface IMyForm {
  dpt: string;
  member: Member;
}

// 组件中的代码
export class HelloComponent implements OnInit  {

  form: FormGroupTypeSafe<IHeroFormModel>;

  constructor (private formHelper: FormBuilderTypeSafe) {}

  ngOnInit(): void {
    this.form = this.formHelper.group<IHeroFormModel>({
      dept: '前端',
      member: { name: '小张', uid: 1 }
    });

    console.log(this.form.value);  // value 为 `IMyForm` 类型
    this.form.getSafe('member');   // 编辑器自动提示'member'属性啦~
  }

}

85eb914d48c428ef4969e19c2a18cfe3.png

可以在stackblitz上看到文章中所有代码——demo

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值