Reputation: 4074
With the new typed form controls in Angular, we can do this:
interface MyFormGroup {
id: FormControl<number | null>;
name: FormControl<string | null>;
email: FormControl<string | null>;
}
Which defines a type for each FormControl
in the following FormGroup
:
myFormGroup = new FormGroup<MyFormGroup>({
id: new FormControl(42),
name: new FormControl('Arthur'),
email: new FormControl('[email protected]')
});
The type of the value of this FormGroup
would be:
Partial<{
id: number | null;
name: string | null;
email: string | null;
}>
If I want to use the value of the FormGroup
in a function, is there a shortcut to getting the type of the value, or must this be defined separately, e.g.
interface MyFormGroupValue {
id: number | null;
name: string | null;
email: string | null;
}
myFunction(myFormGroupValue: MyFormGroupValue){
console.log(myFormGroupValue);
}
Put another way, is it possible to derive the type MyFormGroupValue
from MyFormGroup
?
Upvotes: 9
Views: 4431
Reputation: 330
You can derive the value type with one line of typescript:
type FormValue = FormGroup<ExtremelyComplicatedForm>['value'];
This should do the job no matter how complicated your form is.
Upvotes: 4
Reputation: 11
I currently use the following oneliner:
type ValueOfForm<T extends AbstractControl> = ReturnType<T["getRawValue"]>;
When I need to specify the type in more than one location I also prefer creating a local type alias above the component, like the following:
type FormValue = ValueOfForm<ExampleComponent["form"]>;
@Component({selector: "app-example", template: "" })
export class ExampleComponent {
readonly form = new FormGroup({
name: new FormControl("", {
nonNullable: true,
validators: Validators.required,
}),
});
}
Upvotes: 1
Reputation: 161
This working for me(for all cases).
export type FormGroupTypedValue<
T extends {
[K in keyof T]?: AbstractControl;
}
> = {
[K in keyof T]: RawValue<T[K]>;
};
type RawValue<T extends AbstractControl | undefined> = T extends AbstractControl
? T['setValue'] extends (v: infer R) => void
? R
: never
: never;
Upvotes: 1
Reputation: 19
Extending on Matt Saunders' answer, this will help if you have optional properties in your form, e.g:
// Interface with optional form controls
interface MyFormGroup {
id?: FormControl<number | null>;
name?: FormControl<string | null>;
email?: FormControl<string | null>;
}
Updated ExtractFormControl
to handle optional properties:
export type ExtractFormControl<T> = {
[K in keyof T]
: T[K] extends FormControl<infer U>
? U
: T[K] extends (FormControl<infer U> | undefined)
? (U | undefined)
: T[K] extends FormArray<FormControl<infer U>>
? Array<U>
: T[K] extends (FormArray<FormControl<infer U>> | undefined)
? (Array<U> | undefined)
: T[K] extends FormArray<FormGroup<infer U>>
? Array<Partial<ExtractFormControl<U>>>
: T[K] extends (FormArray<FormGroup<infer U>> | undefined)
? (Array<Partial<ExtractFormControl<U>>> | undefined)
: T[K] extends FormGroup<infer U>
? Partial<ExtractFormControl<U>>
: T[K] extends (FormGroup<infer U> | undefined)
? (Partial<ExtractFormControl<U>> | undefined)
: T[K]
}
Upvotes: 1
Reputation: 4074
Following up on the excellent answer from @Tobias S. If you have a FormGroup
or FormArray
with nested controls, you might need something that uses recursion to traverse the full structure like:
export type ExtractFormControl<T> = {
[K in keyof T]
: T[K] extends FormControl<infer U>
? U
: T[K] extends FormArray<FormControl<infer U>>
? Array<U>
: T[K] extends FormArray<FormGroup<infer U>>
? Array<Partial<ExtractFormControl<U>>>
: T[K] extends FormGroup<infer U>
? Partial<ExtractFormControl<U>>
: T[K]
}
Upvotes: 5
Reputation: 23895
Using a mapped type and the infer
keyword will do the trick.
type ExtractFormControl<T> = {
[K in keyof T]: T[K] extends FormControl<infer U> ? U : T[K]
}
type MyFormGroupValue = ExtractFormControl<MyFormGroup>
// type MyFormGroupValue = {
// id: number | null;
// name: string | null;
// email: string | null;
// }
Upvotes: 13