Matt Saunders
Matt Saunders

Reputation: 4074

Deriving the type of the value of a typed FormGroup in Angular

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

Answers (6)

Schnitzler
Schnitzler

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

ChristofferGersen
ChristofferGersen

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

Taras Shevhyk
Taras Shevhyk

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

Sebastian Roschi
Sebastian Roschi

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

Matt Saunders
Matt Saunders

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

Tobias S.
Tobias S.

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;
// }

Playground

Upvotes: 13

Related Questions