{"id":13809019,"url":"https://github.com/iamguid/ngx-mf","last_synced_at":"2026-01-25T05:31:58.024Z","repository":{"id":43863904,"uuid":"511704395","full_name":"iamguid/ngx-mf","owner":"iamguid","description":"Bind your model types to angular FormGroup type","archived":false,"fork":false,"pushed_at":"2024-12-05T22:45:11.000Z","size":326,"stargazers_count":44,"open_issues_count":4,"forks_count":2,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-09-12T15:09:03.814Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/iamguid.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-07-07T23:42:22.000Z","updated_at":"2025-07-24T14:36:48.000Z","dependencies_parsed_at":"2024-12-20T18:07:18.104Z","dependency_job_id":"393d47dd-48d4-413f-b0e1-829809bd0e60","html_url":"https://github.com/iamguid/ngx-mf","commit_stats":{"total_commits":114,"total_committers":2,"mean_commits":57.0,"dds":"0.052631578947368474","last_synced_commit":"dd7faa07f5d46bdd939fbb0661b653b2e4603dfe"},"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/iamguid/ngx-mf","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamguid%2Fngx-mf","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamguid%2Fngx-mf/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamguid%2Fngx-mf/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamguid%2Fngx-mf/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/iamguid","download_url":"https://codeload.github.com/iamguid/ngx-mf/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamguid%2Fngx-mf/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28744429,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-25T05:12:38.112Z","status":"ssl_error","status_checked_at":"2026-01-25T05:04:50.338Z","response_time":113,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-04T01:01:57.986Z","updated_at":"2026-01-25T05:31:58.000Z","avatar_url":"https://github.com/iamguid.png","language":"TypeScript","funding_links":[],"categories":["Underlying Technologies"],"sub_categories":["TypeScript"],"readme":"# ngx-mf\n`ngx-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.\n\nIt doesn't increase your bundle size because it's just TypeScript types.\n\n## Playground is here\nYou can play around using this link:\nhttps://stackblitz.com/edit/ngx-mf-playground?file=src%2Fmain.ts\n\n## Installation\n\nnpm\n\n```bash\n$ npm i ngx-mf\n```\n\nyarn\n\n```bash\n$ yarn add ngx-mf\n```\n\n## How It Works\n\nDefine some model:\n\n```typescript\nenum ContactType {\n    Email,\n    Telephone,\n}\n\ninterface IContactModel {\n    type: ContactType;\n    contact: string;\n}\n\ninterface IUserModel {\n    id?: number;\n    firstName: string;\n    lastName: string;\n    nickname: string;\n    birthday: Date;\n    contacts: IContactModel[];\n}\n```\n\nThen define your form type based on IUserModel:\n\n```typescript\ntype Form = FormType\u003cIUserModel, { contacts: [FormElementGroup] }\u003e\n```\n\nThen you have form type, before form will be defined:\n\n```typescript\nForm[T] is FormGroup\u003c{\n    id?: FormControl\u003cnumber | undefined\u003e | undefined; \n    firstName: FormControl\u003cstring | null\u003e;\n    lastName: FormControl\u003cstring | null\u003e;\n    nickname: FormControl\u003cstring | null\u003e;\n    birthday: FormControl\u003cDate | null\u003e;\n    contacts: FormArray\u003cFormGroup\u003c{\n        type: FormControl\u003cContactType | null\u003e;\n        contact: FormControl\u003cstring | null\u003e;\n    }\u003e\u003e;\n}\u003e\n```\n\n## Usage\n\n`ngx-mf` exports types `FormModel` and `FormType`\n\n`FormModel\u003cTModel, TAnnotations\u003e` - WARNING (deprecated) recursively turns `TModel` fields (where `TModel` is your model type) into a `FormGroup`, `FormArray` or `FormControl`.\nYou can choose what do you want: `FormGroup`, `FormArray` or `FormControl` by annotation.\nYou can pass `TAnnotations` as the second argument to specify output type using special easy to use syntax.\n\n`FormType` needs to get types of nested fields.\n\nExample model from How It Works chapter:\n\n```typescript\nenum ContactType {\n    Email,\n    Telephone,\n}\n\ninterface IContactModel {\n    type: ContactType;\n    contact: string;\n}\n\ninterface IUserModel {\n    id?: number;\n    firstName: string;\n    lastName: string;\n    nickname: string;\n    birthday: Date;\n    contacts: IContactModel[];\n}\n```\n\nLets say we want infer `FormGroup` where fields `firstName`, `lastName`, `nickname`, `birthday` should be `FormControl` and field `contacts` should be `FormArray` of `FormGroups`.\n\nFor that we need to pass annotation in our `FormType` type.\n The syntax of annotation will be:\n\n```typescript\n{ contacts: [FormElementGroup] }\n```\n\nWhere `contacts` is our field, `[FormElementGroup]` indicates that field is `FormArray`.\n`[FormElementGroup]` indicates that we have `FormGroup` inside `FormArray`.\n\nSo our full `UserForm` type should be:\n```typescript\ntype UserForm = FormType\u003cIUserModel, { contacts: [FormElementGroup] }\u003e\n```\n\nYou can find full example\nhere [/tests/example.test.mts](https://github.com/iamguid/ngx-mf/blob/master/tests/example.test.mts)\n\n`FormType\u003cTModel, TAnnotations\u003e` - Recursively turns `TModel` fields (where `TModel` is your model type) into types tree with your model structure and additional fields for shortcuts.\nThere is 3 type of shortcuts:\n* `T` - type of full form for current node, something like `FormGroup\u003c...\u003e`\n* `G` - group type of your FromGroup, looks like `{a: FromControl\u003c...\u003e, b: FormControl\u003c...\u003e}`\n* `I` - array item type of your FormArray, looks like `FormControl\u003c...\u003e`\n\nYou can combine keys of your model and this additional fields for every level of your type to get type that you need.\n\nI strongly recommend to use `FormType`, because in specific cases you may need to get form type for nested fields,\nand sometime this fields are optional, and it will be difficult to get type of nested optional field.\n\n## Annotations\n`ngx-mf` annotations have three different annotations: `FormElementArray`, `FormElementGroup`, `FormElementControl`\n\n* `FormElementArray` - infer `FormArray` on the same nesting\n* `FormElementGroup` - infer `FormGroup` on the same nesting\n* `FormElementControl` - infer `FormControl` on the same nesting\n\nAlso annotations can be objects, like `{a: FormElementGroup}`,\nand arrays, like `[FormElementGroup]`.\n\nIf you use `{}` then object with the same nesting will be `FormGroup`\nIf you use `[]` then object with the same nesting will be `FormArray`\n\nAnd you can combine `keys of TModel`, `{}`, `[]`, `FormElementArray`, `FormElementGroup`, `FormElementControl`\nto specify what you do want to infer in result type.\n\nCheck [/tests/annotations.test.mts](https://github.com/iamguid/ngx-mf/blob/master/tests/annotations.test.mts) for details\n\n## Examples Of Usage\n\n\u003e Definition of example model:\n\u003e \n\u003e ```typescript\n\u003e interface Model {\n\u003e     a: number | null;\n\u003e     b?: {\n\u003e         c: {\n\u003e             d: number[];\n\u003e             e: { \n\u003e                 f: string; \n\u003e             }\n\u003e         }[]\n\u003e     }\n\u003e }\n\u003e ```\n\n---\n\nLets see what `FormType` will do without annotations\n\n\u003e ```typescript\n\u003e type Form = FormType\u003cModel\u003e\n\u003e ```\n\u003e\n\u003e ```typescript\n\u003e Form[T] is FormGroup\u003c{\n\u003e     a: FormControl\u003cnumber | null\u003e;\n\u003e     b: FormControl\u003cstring[]\u003e;\n\u003e     c?: FormControl\u003c{\n\u003e         d: {\n\u003e            e: number[];\n\u003e            f: {\n\u003e                g: string;\n\u003e            };\n\u003e         }[];\n\u003e     } | undefined\u003e | undefined;\n\u003e }\u003e\n\u003e ```\n\nAs you can see root is `FormGroup`, and elements is `FormControl` - it is the default behavior of `FormType` without annotations\n\nAs you can see `c` field is optional, because in `Model` this field marked as optional in form type too.\nThat means, all optionals will be optionals in inferred type.\n\n---\n\nNow let's say that `c` should be `FormGroup`\n\n\u003e ```typescript\n\u003e type Form = FormType\u003cModel, { c: FormElementGroup }\u003e\n\u003e ```\n\u003e\n\u003e ```typescript\n\u003e Form[T] is FormGroup\u003c{\n\u003e     a: FormControl\u003cnumber | null\u003e;\n\u003e     b: FormControl\u003cstring\u003e;\n\u003e     c?: FormGroup\u003c{ // \u003c\u003c\n\u003e         d: FormControl\u003c{ // \u003c\u003c\n\u003e            e: number[];\n\u003e            f: {\n\u003e                g: string;\n\u003e            };\n\u003e         }[]\u003e;\n\u003e     } | undefined\u003e | undefined;\n\u003e }\u003e\n\u003e ```\n---\n\nNow let's say that `c.d` should be `FormArray`\n\n\u003e ```typescript\n\u003e type Form = FormType\u003cModel, { c: { d: FormElementArray } }\u003e\n\u003e ```\n\u003e\n\u003e ```typescript\n\u003e Form[T] is FormGroup\u003c{\n\u003e     a: FormControl\u003cnumber | null\u003e;\n\u003e     b: FormControl\u003cstring[]\u003e;\n\u003e     c?: FormGroup\u003c{ // \u003c\u003c\n\u003e         d: FormArray\u003cFormControl\u003c{ // \u003c\u003c\n\u003e            e: number[];\n\u003e            f: {\n\u003e                g: string;\n\u003e            };\n\u003e         }\u003e\u003e;\n\u003e     } | undefined\u003e | undefined;\n\u003e }\u003e\n\u003e ```\n\n---\n\nNow let's say that `c.d.e` should be `FormArray`\n\n\u003e ```typescript\n\u003e type Form = FormType\u003cModel, { c: { d: [ { e: FormElementArray } ] } }\u003e\n\u003e ```\n\u003e\n\u003e ```typescript\n\u003e Form[T] is FormGroup\u003c{\n\u003e     a: FormControl\u003cnumber | null\u003e;\n\u003e     b: FormControl\u003cstring[]\u003e;\n\u003e     c?: FormGroup\u003c{ // \u003c\u003c\n\u003e         d: FormArray\u003cFormGroup\u003c{ // \u003c\u003c\n\u003e            e: FormArray\u003cFormControl\u003cnumber\u003e\u003e; // \u003c\u003c\n\u003e            f: {\n\u003e                g: string;\n\u003e            };\n\u003e         }\u003e\u003e;\n\u003e     } | undefined\u003e | undefined;\n\u003e }\u003e\n\u003e ```\n\n---\n\nNow let's say that `c.d.e` should be `FormArray` and `c.d.f` should be `FormGroup`\n\n\u003e ```typescript\n\u003e type Form = FormType\u003cModel, { c: { d: [ { e: FormElementArray, f: FormElementGroup } ] } }\u003e\n\u003e ```\n\u003e\n\u003e ```typescript\n\u003e Form[T] is FormGroup\u003c{\n\u003e     a: FormControl\u003cnumber | null\u003e;\n\u003e     b: FormControl\u003cstring[]\u003e;\n\u003e     c?: FormGroup\u003c{ // \u003c\u003c\n\u003e         d: FormArray\u003cFormGroup\u003c{ // \u003c\u003c\n\u003e            e: FormArray\u003cFormControl\u003cnumber\u003e\u003e; // \u003c\u003c\n\u003e            f: FormGroup\u003c{ // \u003c\u003c\n\u003e                g: FormControl\u003cstring\u003e; // \u003c\u003c\n\u003e            }\u003e;\n\u003e         }\u003e\u003e;\n\u003e     } | undefined\u003e | undefined;\n\u003e }\u003e\n\u003e ```\n\n\n\n---\n\n\u003e If you pass array type to FormType then you will get FormArray\n\u003e instead of FormGroup\n\u003e\n\u003e ```typescript\n\u003e type Form = FormType\u003cnumber[]\u003e\n\u003e ```\n\u003e \n\u003e would be\n\u003e\n\u003e ```typescript\n\u003e FormArray\u003cFormControl\u003cnumber\u003e\u003e\n\u003e ```\n\n\u003e Also you can define FormArray recursively like group inside\n\u003e array inside array :)\n\u003e ```typescript\n\u003e type Form = FormType\u003cSomeModel, [[FormElementGroup]]\u003e\n\u003e ```\n\n\u003e Or array inside group inside array for example:\n\u003e \n\u003e ```typescript\n\u003e type Form = FormType\u003cSomeModel, [{a: FormElementArray}]\u003e\n\u003e ```\n\nOther examples you can find in annotation tests\n[/tests/annotations.test.mts](https://github.com/iamguid/ngx-mf/blob/master/tests/annotations.test.mts)\n\n## The right way to debug your types\n\n* Always use `FormGroup\u003cFormType[G]\u003e` types when you create your form group.\nBecause it will be more simpler to debug wrong types, and it allow you to not to specify controls types directly.\nSee answer here https://github.com/iamguid/ngx-mf/issues/19\n\n* Use FormBuilder (`fb.group\u003cForm[G]\u003e(...)`) or constructor (`new FormGroup\u003cForm[G]\u003e(...)`) syntax to define your forms.\nBecause if you use array syntax, then you can't pass argument to FormGroup type.\n\n## Questions\n\n\u003e Q: Why i can't use just `FormGroup\u003cModel\u003e` ?\n\u003e \n\u003e A: Because when your model have nested fields, then it won't work\n\n\u003e Q: Why i can't define form just as `FormGroup` ?\n\u003e \n\u003e A: Because then you loose your types\n\n\u003e Q: Why i can't define forms without binding\n\u003e it to model type ?\n\u003e \n\u003e A: Yes you can, but it's more usefull to bind it, because if you change the model it will better to see inconsystency directly in your form\n\n\u003e Q: Why i can't init form when define it and use `typeof` to infer form type?\n\u003e \n\u003e A: Yes you can, it is another way to save form type\n\u003e and you can use `typeof` to get type of form,\n\u003e to pass it to the method, but when your model will\n\u003e change then you will see errors only in the places\n\u003e where you use `patch` \u003e or `setValue`,\n\u003e but it is inderect errors, and when you bind\n\u003e forms to models you will see errors on the form definition.\n\u003e But, anyway in that case you can't to get types of your form before\n\u003e it will be define\n\n\u003e Q: What about dynamic forms ?\n\u003e \n\u003e A: you can make some fields optional and enable/disable\n\u003e it when you need it.\n\n\u003e Q: What about complicated forms that includes many of fields, groups and controls\n\u003e \n\u003e A: It is the main scenario of `ngx-mf`\n\n## Links\n* Reddit topic - https://www.reddit.com/r/angular/comments/vv2xmd/what_do_you_think_about_generating_formgroup_type/\n* Stackoverflow questions - https://stackoverflow.com/questions/72500855/formbuilder-with-strongly-typed-form-in-ng14 https://stackoverflow.com/questions/72507263/angular-14-strictly-typed-reactive-forms-how-to-type-formgroup-model-using-exi\n* Dev.to article - https://dev.to/iamguid/new-way-to-cook-angular-14-typed-forms-1g7h\n* Medium article - https://medium.com/p/1ffebf193df\n* Playground - https://stackblitz.com/edit/ngx-mf-playground?file=src%2Fmain.ts","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fiamguid%2Fngx-mf","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fiamguid%2Fngx-mf","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fiamguid%2Fngx-mf/lists"}