Bind your model types to angular FormGroup type
npm install ngx-mfngx-mf is a small (100 lines of code) zero dependency set of TypeScript types for recursive infer angular FormGroup, FormArray or FormControl type from your model type.It doesn't increase your bundle size because it's just TypeScript types.
npm
``bash`
$ npm i ngx-mf
yarn
`bash`
$ yarn add ngx-mf
Define some model:
`typescript
enum ContactType {
Email,
Telephone,
}
interface IContactModel {
type: ContactType;
contact: string;
}
interface IUserModel {
id?: number;
firstName: string;
lastName: string;
nickname: string;
birthday: Date;
contacts: IContactModel[];
}
`
Then define your form type based on IUserModel:
`typescript`
type Form = FormType
Then you have form type, before form will be defined:
`typescript`
Form[T] is FormGroup<{
id?: FormControl
firstName: FormControl
lastName: FormControl
nickname: FormControl
birthday: FormControl
contacts: FormArray
contact: FormControl
}>>;
}>
ngx-mf exports types FormModel and FormType
FormModel - WARNING (deprecated) recursively turns TModel fields (where TModel is your model type) into a FormGroup, FormArray or FormControl.FormGroup
You can choose what do you want: , FormArray or FormControl by annotation.TAnnotations
You can pass as the second argument to specify output type using special easy to use syntax.
FormType needs to get types of nested fields.
Example model from How It Works chapter:
`typescript
enum ContactType {
Email,
Telephone,
}
interface IContactModel {
type: ContactType;
contact: string;
}
interface IUserModel {
id?: number;
firstName: string;
lastName: string;
nickname: string;
birthday: Date;
contacts: IContactModel[];
}
`
Lets say we want infer FormGroup where fields firstName, lastName, nickname, birthday should be FormControl and field contacts should be FormArray of FormGroups.
For that we need to pass annotation in our FormType type.
The syntax of annotation will be:
`typescript`
{ contacts: [FormElementGroup] }
Where contacts is our field, [FormElementGroup] indicates that field is FormArray.[FormElementGroup] indicates that we have FormGroup inside FormArray.
So our full UserForm type should be:`typescript`
type UserForm = FormType
You can find full example
here /tests/example.test.mts
FormType - Recursively turns TModel fields (where TModel is your model type) into types tree with your model structure and additional fields for shortcuts.T
There is 3 type of shortcuts:
* - type of full form for current node, something like FormGroup<...>G
* - group type of your FromGroup, looks like {a: FromControl<...>, b: FormControl<...>}I
* - array item type of your FormArray, looks like FormControl<...>
You can combine keys of your model and this additional fields for every level of your type to get type that you need.
I strongly recommend to use FormType, because in specific cases you may need to get form type for nested fields,
and sometime this fields are optional, and it will be difficult to get type of nested optional field.
annotations have three different annotations: FormElementArray, FormElementGroup, FormElementControl*
FormElementArray - infer FormArray on the same nesting
* FormElementGroup - infer FormGroup on the same nesting
* FormElementControl - infer FormControl on the same nestingAlso annotations can be objects, like
{a: FormElementGroup},
and arrays, like [FormElementGroup].If you use
{} then object with the same nesting will be FormGroup
If you use [] then object with the same nesting will be FormArrayAnd you can combine
keys of TModel, {}, [], FormElementArray, FormElementGroup, FormElementControl
to specify what you do want to infer in result type.Check /tests/annotations.test.mts for details
Examples Of Usage
> Definition of example model:
>
>
`typescript
> interface Model {
> a: number | null;
> b?: {
> c: {
> d: number[];
> e: {
> f: string;
> }
> }[]
> }
> }
> `---
Lets see what
FormType will do without annotations>
`typescript
> type Form = FormType
> `
>
> `typescript
> Form[T] is FormGroup<{
> a: FormControl;
> b: FormControl;
> c?: FormControl<{
> d: {
> e: number[];
> f: {
> g: string;
> };
> }[];
> } | undefined> | undefined;
> }>
> `As you can see root is
FormGroup, and elements is FormControl - it is the default behavior of FormType without annotationsAs you can see
c field is optional, because in Model this field marked as optional in form type too.
That means, all optionals will be optionals in inferred type.---
Now let's say that
c should be FormGroup>
`typescript
> type Form = FormType
> `
>
> `typescript
> Form[T] is FormGroup<{
> a: FormControl;
> b: FormControl;
> c?: FormGroup<{ // <<
> d: FormControl<{ // <<
> e: number[];
> f: {
> g: string;
> };
> }[]>;
> } | undefined> | undefined;
> }>
> `
---Now let's say that
c.d should be FormArray>
`typescript
> type Form = FormType
> `
>
> `typescript
> Form[T] is FormGroup<{
> a: FormControl;
> b: FormControl;
> c?: FormGroup<{ // <<
> d: FormArray > e: number[];
> f: {
> g: string;
> };
> }>>;
> } | undefined> | undefined;
> }>
> `---
Now let's say that
c.d.e should be FormArray>
`typescript
> type Form = FormType
> `
>
> `typescript
> Form[T] is FormGroup<{
> a: FormControl;
> b: FormControl;
> c?: FormGroup<{ // <<
> d: FormArray > e: FormArray>; // <<
> f: {
> g: string;
> };
> }>>;
> } | undefined> | undefined;
> }>
> `---
Now let's say that
c.d.e should be FormArray and c.d.f should be FormGroup>
`typescript
> type Form = FormType
> `
>
> `typescript
> Form[T] is FormGroup<{
> a: FormControl;
> b: FormControl;
> c?: FormGroup<{ // <<
> d: FormArray > e: FormArray>; // <<
> f: FormGroup<{ // <<
> g: FormControl; // <<
> }>;
> }>>;
> } | undefined> | undefined;
> }>
> `---
> If you pass array type to FormType then you will get FormArray
> instead of FormGroup
>
>
`typescript
> type Form = FormType
> `
>
> would be
>
> `typescript
> FormArray>
> `> Also you can define FormArray recursively like group inside
> array inside array :)
>
`typescript
> type Form = FormType
> `> Or array inside group inside array for example:
>
>
`typescript
> type Form = FormType
> `Other examples you can find in annotation tests
/tests/annotations.test.mts
The right way to debug your types
* Always use
FormGroup types when you create your form group.
Because it will be more simpler to debug wrong types, and it allow you to not to specify controls types directly.
See answer here https://github.com/iamguid/ngx-mf/issues/19* Use FormBuilder (
fb.group