{"id":21657512,"url":"https://github.com/navix/ngfe","last_synced_at":"2025-04-11T22:33:02.204Z","repository":{"id":39586551,"uuid":"380226631","full_name":"navix/ngfe","owner":"navix","description":"🧰 Boosted template-driven Angular forms.","archived":false,"fork":false,"pushed_at":"2025-03-12T09:02:06.000Z","size":886,"stargazers_count":3,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-03T11:01:52.519Z","etag":null,"topics":["angular","forms","template-driven-forms"],"latest_commit_sha":null,"homepage":"https://stackblitz.com/edit/ngfe-showcase?file=src/app/app.component.html","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/navix.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":"2021-06-25T12:10:30.000Z","updated_at":"2025-03-12T08:58:40.000Z","dependencies_parsed_at":"2025-03-23T17:00:55.425Z","dependency_job_id":"26f81511-837c-4588-8306-fcb3ed8c408b","html_url":"https://github.com/navix/ngfe","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/navix%2Fngfe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/navix%2Fngfe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/navix%2Fngfe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/navix%2Fngfe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/navix","download_url":"https://codeload.github.com/navix/ngfe/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248490022,"owners_count":21112675,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["angular","forms","template-driven-forms"],"created_at":"2024-11-25T09:26:45.599Z","updated_at":"2025-04-11T22:33:02.182Z","avatar_url":"https://github.com/navix.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![npm version](https://badge.fury.io/js/ngfe.svg)](https://www.npmjs.com/package/ngfe)\n![CI](https://github.com/navix/ngfe/actions/workflows/ci.yml/badge.svg)\n\n# 🧰 ngfe | Angular Forms Extra\n\nBoosted template-driven Angular forms.\n\nIt is an alternative for the Angular `FormsModule`.\nIf your project have complex and dynamic forms this lib will save you a lot of time and lines of code.\n\n\u003e [StackBlitz showcase](https://stackblitz.com/edit/ngfe-showcase?file=src/app/app.component.html)\n\n## Features\n\n* **Focused on template-driven approach.**\n* **Less abstractions, ultimate control.**\n* **More freedom for developers.**\n* Nothing exceptionally new for Angular people.\n* Less boilerplate to write:\n  * Simple custom value accessors creation.\n  * Simple custom validators creation.\n  * Single interface for sync and async validators.\n  * No `ControlContainer` providing for sub-forms.\n  * No required `name` binding.\n  * Directive for easy value init/cleanup on dynamic forms.\n  * Handy way to display validation errors only on touched fields.\n* Function validators binding.\n* Built-in debounce.\n* Adapters for two-way value conversion.\n* Two-way state binding in templates (e.g `[(touched)]`).\n* Almost all states have reactive alternative (e.g `.errors`+`.errors$`).\n* Submit directive which touches all fields and checks validity.\n* Stricter types in controls.\n* `OnPush` mode support.\n* SSR support.\n* Zero deps, only Angular and RxJS.\n* Reduced bundle size without @angular/forms (~20KB parsed size in prod mode).\n* Does not conflict with the Angular `FormsModule`.\n* Optional integration with Angular `Validator` and `ValueAccessor` interfaces.\n* Works with Angular Material.\n\n### Caveats\n\n* 3rd party lib.\n* Not battle-tested enough yet.\n* Sometimes too much freedom for developers.\n\n### Why template forms\n\n* **Single source of truth for your forms - templates.**\n* Almost all logic written in a declarative manner.\n* Less code to write.\n* https://www.youtube.com/watch?v=L7rGogdfe2Q\n\n\n## Terms\n\n* **Form** - tool for displaying and manipulating data in Browser.\n* **Model** - variable that represents a field of data.\n* **Input** - HTML element (or custom component) allows you to display and change some state.\n* **Control** - a bridge between **Model** and **Input**.\n* **Value accessor** - directive or component that connects **Input** to the **Control**.\n* **Adapter** - functions that convert state when it flows between **Model** and **Input**. \n* **Validator** - function to check **Model** or **Input** values to meet some conditions.\n* **Error** - returner by **Validator** if value is invalid. \n* **Validity** - represents current validation state: \n  * `pending` - one or more async **Validators** are running,\n  * `invalid` - one or more **Validators** returned errors,\n  * `valid` - all **Validators** returned no errors.\n* **Touched** - **Input** had interaction with user (was focused for built-in **Value accessors**).\n* **Dirty** - **Input** was changed by user.\n\n\n\n## Installation\n\n```\n$ npm i ngfe\n```\n\n* `ngfe@13` for Angular 12 and 13. RxJS 7 needed.\n\n\n\n## Usage\n\nImport the module:\n\n```\nimport { FeModule } from 'ngfe';\n...\nimports: [\n  FeModule,\n  ...\n]\n```\n\nAlso, all directives are standalone and can be imported separately:\n\n```\nimports: [FeControl, FeInput, FeRequiredValidator]\n```\n\n\n\n## Binding\n\nOn the surface [`[(feControl)]`](./projects/ngfe/src/lib/core/fe-control.ts) works exactly like `[(ngModel)]`.\n\n```\n\u003cinput [(feControl)]=\"field\"\u003e\n```\n\n## Built-in value accessors\n\n### [Input](./projects/ngfe/src/lib/value-accessors/fe-input.ts)\n\n```\n\u003cinput [(feControl)]=\"field\"\u003e\n\u003cinput [(feControl)]=\"field2\" type=\"checkbox\"\u003e\n\u003cinput [(feControl)]=\"field3\" type=\"radio\" value=\"1\"\u003e\n\u003cinput [(feControl)]=\"field4\" type=\"date\" value=\"1\"\u003e\n...\n```\n\n\u003e [[StackBlitz] ngfe inputs demo](https://stackblitz.com/edit/ngfe-inputs-demo?file=src/app/app.component.html)\n\n#### File helpers\n\nThere is a built-in function [`readFiles`](./projects/ngfe/src/lib/util/read-files.ts) to read file data from file inputs:\n\n```\n\u003cinput (feControlChange)=\"loadFiles($event)\" type=\"file\"\u003e\n```\n\n```\nimport { readFiles } from 'ngfe';\n...\nloadFiles(files?: FileList) {\n  readFiles(files || []).subscribe(loadedFiles =\u003e {\n    ...\n    this.cdr.markForCheck();\n  });\n}\n```\n\n#### Touch on change\n\nYou can control how touched state is set with `touchOnChange` and `touchOnBlur` parameters.\n\nBy default `touchOnBlur` is `true` and `touchOnChange` is `false`. \n\n```\n\u003cinput [(feControl)]=\"field1\" touchOnChange\u003e\n\u003cinput [(feControl)]=\"field2\" [touchOnBlur]=\"false\"\u003e\n```\n\n### [Textarea](./projects/ngfe/src/lib/value-accessors/fe-input.ts)\n\n```\n\u003ctextarea [(feControl)]=\"field\"\u003e\u003c/textarea\u003e\n```\n\n### [Select](./projects/ngfe/src/lib/value-accessors/fe-select.ts)\n\n```\n\u003cselect [(feControl)]=\"field\"\u003e\n  \u003coption value=\"1\"\u003eONE\u003c/option\u003e\n  \u003coption value=\"2\"\u003eTWO\u003c/option\u003e\n\u003c/select\u003e\n```\n\nAny type of value available to bind to `option[value]`.\n\n```\nfield: number;\n```\n\n```\n\u003cselect [(feControl)]=\"field\"\u003e\n  \u003coption [value]=\"1\"\u003eONE\u003c/option\u003e\n  \u003coption [value]=\"2\"\u003eTWO\u003c/option\u003e\n\u003c/select\u003e\n```\n\n\n\n## [Adapters](./projects/ngfe/src/lib/core/adapters.ts)\n\nControls store 2 values at the same moment: `modelValue` and `inputValue`. When `modelValue` changes its' value also transferred to `inputValue` and vice-versa.  You could define functions that change the values during this transition. \n\nAt the first place this feature is needed to keep proper types for values in models.\n\nFor example:\n\n```\nfield: number = 100;\n```\n\n```\n\u003cinput [(feControl)]=\"field\" adapter=\"numberToString\"\u003e\n```\n\n_Note: value accessor for `input[type=\"number\"]` parses input and returns number without the adapter._\n\nOr native Date:\n\n```\nfield = new Date();\n```\n\n```\n\u003cinput [(feControl)]=\"field\" type=\"date\" adapter=\"dateToDateString\"\u003e\n```\n\n_By default in browsers date input uses `string`._\n\n### [Built-in adapters](./projects/ngfe/src/lib/core/adapters.ts)\n\n* `numberToString` - keeps number in model and string in input.\n* `dateToDateString` - useful for inputs with type `date`.\n* `dateToDateLocalString` - useful for inputs with type `date-local`.\n* `deepCopy` - useful for objects and arrays.\n\n### Custom adapter\n\nUse [`FeAdapter`](./projects/ngfe/src/lib/core/adapters.ts) interface to declare modifying functions:\n\n```\nbooleanToString: FeAdapter\u003cboolean, string\u003e = {\n  fromModel: modelValue =\u003e modelValue === true ? '1' : modelValue === false ? '0' : '',\n  fromInput: inputValue =\u003e inputValue === '1' ? true : inputValue === '0' ? false : undefined,\n};\n```\n\nPass it to `[adapter]` input:\n\n```\n\u003cinput [(feControl)]=\"field\" [adapter]=\"booleanToString\"\u003e\n```\n\n\u003e [[StackBlitz] ngfe custom adapter demo](https://stackblitz.com/edit/ngfe-custom-adapter-demo?file=src/app/app.component.ts)\n\n\n\n## [Validation](./projects/ngfe/src/lib/core/validation.ts)\n\nWork very similar to the default Angular validation.\n\n```\n\u003cinput #control=\"feControl\" [(feControl)]=\"field\" required\u003e\n\u003cdiv *ngIf=\"control.errors as errors\"\u003e\n  \u003cspan *ngIf=\"errors.required\"\u003eRequired\u003c/span\u003e\n\u003c/div\u003e\n```\n\n\u003e [[StackBlitz] ngfe validation demo](https://stackblitz.com/edit/ngfe-validation-demo?file=src/app/app.component.html)\n\n### Visible Errors\n\nAlso, there is `.visibleErrors` that passes errors object when control becomes touched.\n\n```\n\u003cinput #control=\"feControl\" [(feControl)]=\"field\" required\u003e\n\u003cdiv *ngIf=\"control.visibleErrors as errors\"\u003e\n  \u003cspan *ngIf=\"errors.required\"\u003eField is required\u003c/span\u003e\n\u003c/div\u003e\n```\n\n### [Built-in validators](./projects/ngfe/src/lib/validators)\n\n* `required`\n* `email`\n* `equal`, `notEqual`\n* `minlength`, `maxlength` - works only for strings and arrays in `modelValue`.\n* `min`, `max` - works only for numbers in `modelValue`.\n* `pattern`\n* `isNumber` - checks that `inputValue` represents a number or a string that can be parsed to number.\n\n### Custom validator\n\n#### As a function\n\nUse [`FeValidator`](./projects/ngfe/src/lib/core/validation.ts) interface to implement a validator. Return errors object [`FeError`](./projects/ngfe/src/lib/core/validation.ts) or `undefined` if value is valid.\n\n```\n// Invalid if value is not empty and have value \"BOOM\".\nnotBoom: FeValidator\u003cstring\u003e = ({modelValue}) =\u003e {\n  return !modelValue || modelValue !== 'BOOM'\n    ? undefined\n    : {notBoom: true};\n};\n```\n\nPass it to `[extraValidators]` input:\n\n```\n\u003cinput #control=\"feControl\" [(feControl)]=\"field\" [extraValidators]=\"[notBoom]\"\u003e\n\u003cspan *ngIf=\"control.errors?.notBoom\"\u003eValue should not be \"BOOM\"\u003c/span\u003e\n```\n\n#### As a directive\n\nOr, create a validator directive:\n\n```\n@Directive({\n  selector: '[feControl][notBoom]'\n})\nexport class notBoomValidatorDirective implements OnChanges {\n  constructor(\n    @Self() private control: FeControl\u003cstring\u003e,\n  ) {\n    this.control.addValidator(({modelValue}) =\u003e {\n      return !modelValue || modelValue !== 'BOOM'\n        ? undefined\n        : {notBoom: true};\n    });\n  }\n}\n```\n\n```\n\u003cinput [(feControl)]=\"field\" notBoom\u003e\n```\n\n\u003e [[StackBlitz] ngfe validation demo](https://stackblitz.com/edit/ngfe-validation-demo?file=src/app/not-boom-validator.directive.ts)\n\n### Async validators\n\nJust return from validation function `Observable` or `Promise` with [`FeValidatorResult`](./projects/ngfe/src/lib/core/validation.ts).\n\n\n\n## Debounce \n\nDefine debounce time for values from a value accessor:b\n\n```\n\u003cinput [(feControl)]=\"field\" [debounce]=\"400\"\u003e\n```\n\n\n\n## [Submit](./projects/ngfe/src/lib/core/fe-submit.ts)\n\nDirective that marks all form controls as touched, when user submits the form.\n\nAlso emits event only if form has `valid` state.\n\n### On button\n\n```\n\u003cform\u003e\n  ...\n  \u003cbutton (feSubmit)=\"doStuff()\"\u003eSubmit\u003c/button\u003e\n\u003c/form\u003e\n```\n\n### On form\n\n```\n\u003cform (feSubmit)=\"doStuff()\"\u003e\n  ...\n\u003c/form\u003e\n```\n\n\n\n## [Init/cleanup values](./projects/ngfe/src/lib/core/fe-if.ts)\n\nFor dynamic forms we need to setup values when some fields became visible, and remove such values on field hiding.\n\nStructural directive [`feIf`](./projects/ngfe/src/lib/core/fe-if.ts) works similar to `ngIf` (except `else` part) and could set a model to some default / `undefined`.\n\nWhen Angular change detection runs, `feIf` directive checks that the condition is true/false, wait until template updates, then update bound model and renders conditional template. This allows us to keep this logic in template and not collide with rendering process.\n\n_The main disadvantage - it works only with `\u003cng-template\u003e`._\n\n```\n\u003cng-template [feIf]=\"showField\" [(ensure)]=\"field\"\u003e\n  \u003cinput [(feControl)]=\"field\"\u003e\n\u003c/ng-template\u003e\n```\n\nAlso, you could define `[default]` value that will be set to the model when it's empty.\n\n```\n\u003cng-template [feIf]=\"showField\" [(ensure)]=\"field\" default=\"BOOOM\"\u003e\n  \u003cinput [(feControl)]=\"field\"\u003e\n\u003c/ng-template\u003e\n```\n\n\n\n## Custom Value Accessor\n\nUnlike default Angular approach, you do not need to implement `ValueAccessor` interface.\n\nJust inject `FeControl` and use it's props and methods.\n\n`.toInputValue$` - emits all changes except last passed from input itself.\n\n```\n@Component({\n  selector: 'app-custom-control',\n  ...\n})\nexport class AppCustomControlComponent {\n  constructor(private control: FeControl) {\n    this.control.toInputValue$.subscribe(inputValue =\u003e {\n      ...\n    });\n    this.control.disabled$.subscribe(disabled =\u003e {\n      ...\n    });\n  }\n  \n  ...\n  this.control.input(value);\n  ...\n  this.control.touch();\n}\n```\n\n```\n\u003capp-custom-control [(feControl)]=\"field\"\u003e\u003c/app-custom-control\u003e\n```\n\nYou can subscribe to any stream of the control and define any state.\n\n\u003e [[StackBlitz] ngfe custom value accessor demo](https://stackblitz.com/edit/ngfe-custom-va-demo?file=src/app/custom-control.component.ts)\n\n\n\n## [Util](./projects/ngfe/src/lib/util)\n\nSet of functions that is very useful in work with forms.\n\n* [`coerceToBoolean`](./projects/ngfe/src/lib/util/coercion.ts) - coerce an input value (typically a string) to a boolean.\n* [`deepCopy`](./projects/ngfe/src/lib/util/deep-copy.ts) - deep copy objects and arrays.\n* [`diff`](./projects/ngfe/src/lib/util/diff.ts) - compare objects and arrays.\n* [`readFiles`](./projects/ngfe/src/lib/util/read-files.ts) - read file data from File.\n\n\n\n## @angular/forms adapter\n\nEnables an easy transition from Angular forms to **ngfe**.\n\nInstall package:\n\n```\n$ npm i ngfe-ng-adapter\n```\n\nImport module:\n\n```\nimports: [\n  ...\n  FeModule,\n  FeNgAdapterModule,\n]\n```\n\nAfter that you can use Angular `ValueAccessors` and `Validator` with `[(feControl)]`.\n\nAlso, with this package, `feControl` provides `NgControl` and allows you to use **ngfe** with Material components or other UI libs.\n\n\u003e [[StackBlitz] ngfe-ng-adapter demo](https://stackblitz.com/edit/ngfe-ng-adapter?file=src/app/app.component.html)\n\n\n\n## LICENSE \n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnavix%2Fngfe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnavix%2Fngfe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnavix%2Fngfe/lists"}