Angular/TypeScript:基于含嵌套结构的Model生成表单类型
适配嵌套对象与数组的Angular表单泛型类型实现
问题背景
最初的基础User接口及表单类型定义如下:
interface User { firstName: string; lastName: string; gender: 'male' | 'female'; } export type ModelForm<T> = FormGroup<{ [K in keyof T]: FormControl<T[K]>; }>
当使用const myForm: ModelForm<User>时类型正常,但当User接口包含嵌套对象与数组结构时:
interface User { firstName: string; lastName: string; gender: 'male' | 'female'; validity: { validFrom: Date, validUntil: Date | null, } addresses: {street: string, houseNumber:number}[], }
初始的ModelForm泛型无法正确映射嵌套对象(如validity)为FormGroup,也无法将数组(如addresses)映射为FormArray,需要调整泛型逻辑适配复杂结构。
解决方案:递归泛型类型
我们可以通过递归判断类型的方式,让ModelForm自动根据属性类型选择FormControl、FormGroup或FormArray:
import { FormControl, FormGroup, FormArray } from '@angular/forms'; // 辅助类型:判断是否为数组类型 type IsArray<T> = T extends Array<any> ? true : false; // 辅助类型:判断是否为对象类型(排除基础类型与数组) type IsObject<T> = T extends object && !IsArray<T> ? true : false; export type ModelForm<T> = IsArray<T> extends true ? FormArray<ModelForm<T[number]>> : IsObject<T> extends true ? FormGroup<{ [K in keyof T]: ModelForm<T[K]> }> : FormControl<T>;
类型逻辑说明
- 若
T是数组类型:使用FormArray,数组元素的类型为ModelForm<T[number]>(递归处理数组中的每个对象) - 若
T是对象类型(非数组):使用FormGroup,每个属性递归应用ModelForm类型 - 其他基础类型(string、number、Date等):直接使用
FormControl<T>
使用示例
定义表单时直接沿用原写法即可,类型会自动适配嵌套结构:
// 类型会自动推导为包含FormGroup(validity)和FormArray(addresses)的FormGroup const myForm: ModelForm<User> = new FormGroup({ firstName: new FormControl(''), lastName: new FormControl(''), gender: new FormControl<'male' | 'female'>('male'), validity: new FormGroup({ validFrom: new FormControl(new Date()), validUntil: new FormControl<Date | null>(null), }), addresses: new FormArray([ new FormGroup({ street: new FormControl(''), houseNumber: new FormControl(0), }) ]) });
配合FormBuilder使用
实际开发中常用FormBuilder创建表单,类型依然能正确推导:
import { FormBuilder } from '@angular/forms'; constructor(private fb: FormBuilder) { const myForm = this.fb.group({ firstName: '', lastName: '', gender: 'male', validity: this.fb.group({ validFrom: new Date(), validUntil: null, }), addresses: this.fb.array([ this.fb.group({ street: '', houseNumber: 0, }) ]) }) as ModelForm<User>; }
内容的提问来源于stack exchange,提问作者Rose Nettoyeur
相关产品推荐
相关产品推荐

