我目前正在try 创建一个映射类型,该类型从给定的模型类型创建Angular 形状组类型.
我已经创建了一个映射类型,用于判断给定属性是否为数组,然后创建类型为FormArray<T>
的映射属性.问题是枚举数组没有正确映射.
对于类型为MyEnum[]
的属性,我得到的是已解析类型MyEnum.A[] | MyEnum.B[]
,因此不能将枚举值的不同值添加到数组中.
下面是一个带有相同问题的精简版本:
enum Environment{
"EN" = "en",
"US" = "us",
}
interface MyDto {
environments: Environment[];
}
export type FormControlsOf<T> = {
// Check if the property is an array
[TPropertyKey in keyof T]-?: T[TPropertyKey] extends Array<infer TArray>
// I check whether TArray is a record so i can decide whether this should be a FormControl or FormGroup in the real code
? TArray extends Record<any, any> ?
// we ignore records for this example
never
// We map to property to an array; here we use the simple array type but the real code would
// map this to FormArray<FormControl<TArray>>
: TArray[]
// we ignore non arrays for this example
: never;
}
const q: FormControlsOf<MyDto> = {
environments: [Environment.EN, Environment.US]
// The type of environments is actually (Environment.EN[] | Environment.US[])
};
// Error: Type 'Environment[]' is not assignable to type 'Environment.EN[] | Environment.US[]'.
// Type 'Environment[]' is not assignable to type 'Environment.EN[]'.
// Type 'Environment' is not assignable to type 'Environment.EN'.
// Type 'Environment.US' is not assignable to type 'Environment.EN'.(2322)
// input.tsx(8, 3): The expected type comes from property 'environments' which is declared here on type 'FormControlsOf<MyDto>'
如果我删除第13行TArray
上的复选标记,environments
的类型被正确检测为Environment[]
,但我需要该复选标记来确定该属性是FormArray<FormGroup>
还是FormArray<FormControl>
.
编辑:@仙火和@Robby Cornelissen是正确的,分布式条件类型是问题所在.
以下是Angular 形状组映射类型的完整代码:
/**
* This mapped type allows to create types that represent the form group representation of that type.
*
* Example:
* interface MyDto {
* prop1: string;
* prop2: string[];
* }
*
* FormGroupOf<MyDto> will become:
* {
* prop1: FormControl<string>;
* prop2: FormArray<FormControl<string>>;
* }
*
* This allows to define fields that are fully typed.
*
* class TestClass {
*
* public form: FormGroupOf<MyDto>;
*
* constructor() : {
* this.form = new FormGroup({
* prop1: new FormControl(""),
* prop2: new FormArray<FormControl<string>>([]),
* })
* }
* }
*
* Doing it this way allows code completion in both, the template and during the cunstruction of the formgroup.
*
* For complex property types this works recursively.
* interface MyDto2 {
* prop1: {
* innerProp: string;
* };
* }
*
* FormGroupOf<MyDto2> will become:
* {
* prop1: FormGroup<{
* innerProp: FormControl<string|null>;
* }>;
* }
*/
export type FormGroupOf<T> = FormGroup<FormControlsOf<T>>;
export type FormControlsOf<T> = {
[TPropertyKey in keyof T]-?: T[TPropertyKey] extends
| (infer TArray)[]
| undefined
? [TArray] extends [string | number | boolean | undefined]
? FormArray<FormControl<TArray>>
: FormArray<FormGroupOf<TArray>>
: T[TPropertyKey] extends string | number | boolean | undefined
? FormControl<T[TPropertyKey] | null>
: FormGroupOf<T[TPropertyKey]>;
};