I have the following types to support angular form typing:
import { FormArray, FormControl, FormGroup } from '@angular/forms';
export type DeepFormArray<T, U extends boolean | undefined = undefined> = T extends Array<any>
? never
: T extends string | number | boolean
? FormArray<FormControl<T>>
: U extends true
? FormArray<FormControl<T>>
: FormArray<DeepFormGroup<T>>;
export type DeepFormGroup<T> = T extends object
? FormGroup<{
[P in keyof T]: T[P] extends Array<infer K>
? DeepFormArray<K>
: T[P] extends object
? DeepFormGroup<T[P]>
: FormControl<T[P] | null>;
}>
: never;
And I would like to use it on a type, which has a string literal property functioning as a type discriminator. Consider the following example:
interface A {
name: 'A'
}
interface B {
name: 'B'
}
/** infered as
* {name: 'A' | 'B'}
*/
type C = A | B;
interface D {
prop: C[];
}
And I combine these as
// Old example, not accurate enough for my use case,
// but is solved correctly by the modifications
// suggested by @jcalz
/** infered as
* FormGroup<
* {name: FormControl<'A' | null>}
* > | FormGroup<
* {name: FormControl<'B' | null>}
* >
*/
type OldActualType = DeepFormGroup<C>;
type OldNeededType = FormGroup<{name: FormControl<'A' | 'B' | null>}>
// The correct formulation of my problem
/** inferred as
* FormGroup<{
* prop: FormArray<FormGroup<{
* name: FormControl<"A" | null>;
* }>> | FormArray<FormGroup<{
* name: FormControl<"B" | null>;
* }>>;
* }>
*/
type ActualType = DeepFormGroup<D>;
type NeededType = FormGroup<{
prop: FormArray<FormGroup<{
name: FormControl<"A" | "B" | null>;
}>>;
}>
It seems to me that typescript does not create a new type for C
, but treats it as two variants, and when C
is used somewhere, all paths are traversed. Is there a way to modify DeepFormGroup
or the ActualType
definition so the infered type equals to NeededType
? I am on TypeScript 4.8. Here is a TS playground url