基本上给出这两种类型:
type BaseType = FormGroup<{
user: FormControl<string>;
}>;
type SomeOtherType = FormGroup<{
user: FormControl<string>;
amount: FormControl<number>;
}>;
我希望能够将
SomeOtherType
传递给 BaseType
类型的组件输入,因为它们履行了合同。
正常尝试此操作时,我会得到一个看似向后的错误:
Type 'SomeOtherType' is not assignable to type 'BaseType'.
Property 'amount' is missing in type '{ user: FormControl<string>; }' but required in type '{ user: FormControl<string>; amount: FormControl<number>; }'.
我可以通过使用输入变换消除所有限制
@Input({ required: true, transform: (value: FormGroup): BaseType => value })
form!: BaseType;
但是我不喜欢当我希望将任何表单组限制为与基本类型匹配的对象时如何传递任何表单组。
(为了帮助避免任何关于为什么不以其他方式做的问题。这是已经完成的产品的一部分,我只是试图将其从 untyped 表单 everywhere 迁移到新的类型表单.对于非类型化表单,一切功能都完美,并且依赖于表单控件子集的组件没有问题,只是希望能够保留该功能,但同时使用新的类型功能。)
FormGroup 的定义不允许与表单控件的子类型协变。换句话说,虽然
{user: FormControl, amount: FormControl}
是 {user: FormControl}
的子类型,但 FormGroup<{user: FormControl, amount: FormControl}>
不是 FormGroup<{user: FormControl>
的子类型。
FormGroup 扩展了AbstractControl,看来这个问题是由于控件的值是如何导出的。 (这个结论是通过反复试验得出的,因为正在进行的体操是难以理解的。)
由于问题的一部分是转换可能允许不正确的类型,那么至少可以通过添加在转换回调中抛出的断言来防止这种情况。
@Input({
required: true,
transform: (value: FormGroup): BaseType => assertIsBaseType(value) && value
})
form!: BaseType;
/* ... */
function assertIsBaseType(value: FormGroup): true | never {
if (!value.controls['user']) {
throw new Error('value is not Base Type')
}
return true;
}
另一个不太理想的解决方案是将输入声明为 AbstractControl 而不是 FormGroup。只要您不需要特定于 FormGroup 的方法,这就可以工作。
interface BaseTypeValue { user: string; }
interface SomeOtherTypeValue extends BaseTypeValue { amount: number }
type BaseType = FormGroup<{ [K in keyof BaseTypeValue]: FormControl<BaseTypeValue[K]> }>;
type SomeOtherType = FormGroup<{ [K in keyof SomeOtherTypeValue]: FormControl<SomeOtherTypeValue[K]> }>;
/*...*/
@Input({ required: true }) form!: AbstractControl<Partial<BaseTypeValue>, BaseTypeValue>;