{"id":23338741,"url":"https://github.com/appfeel/reactive-forms","last_synced_at":"2026-02-17T17:31:53.781Z","repository":{"id":57240622,"uuid":"437120676","full_name":"appfeel/reactive-forms","owner":"appfeel","description":"Reactive forms web component","archived":false,"fork":false,"pushed_at":"2025-03-21T17:33:26.000Z","size":479,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-09-19T17:45:37.286Z","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/appfeel.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-12-10T21:38:43.000Z","updated_at":"2025-03-21T17:33:22.000Z","dependencies_parsed_at":"2023-01-25T17:15:44.295Z","dependency_job_id":"c179188d-f143-43d8-bcf4-b95642042915","html_url":"https://github.com/appfeel/reactive-forms","commit_stats":{"total_commits":49,"total_committers":4,"mean_commits":12.25,"dds":"0.16326530612244894","last_synced_commit":"d4e63ebcdf17d802ec1089848cdf3141292fddca"},"previous_names":[],"tags_count":16,"template":false,"template_full_name":null,"purl":"pkg:github/appfeel/reactive-forms","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appfeel%2Freactive-forms","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appfeel%2Freactive-forms/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appfeel%2Freactive-forms/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appfeel%2Freactive-forms/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/appfeel","download_url":"https://codeload.github.com/appfeel/reactive-forms/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/appfeel%2Freactive-forms/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29551257,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-17T14:33:00.708Z","status":"ssl_error","status_checked_at":"2026-02-17T14:32:58.657Z","response_time":100,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5: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-12-21T03:16:17.880Z","updated_at":"2026-02-17T17:31:53.753Z","avatar_url":"https://github.com/appfeel.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Reactive Forms Web Component\n\nThis is a reactive form component, [Angular Reactive Forms](https://angular.io/guide/reactive-forms) style.\nIt can be used with any framework of none at all: [Integration](https://stenciljs.com/docs/overview)\n\n# Install\n\n## Pure Javascript\n\nAdd the script in html file:\n```html\n\u003cscript type=\"module\" src=\"https://unpkg.com/forms-reactive/dist/forms-reactive/forms-reactive.esm.js\"\u003e\u003c/script\u003e\n\u003cscript nomodule src=\"https://unpkg.com/forms-reactive/dist/forms-reactive/forms-reactive.js\"\u003e\u003c/script\u003e\n```\n\n## NPM package\n\n```\nnpm i forms-reactive -S\n```\n\n# Usage\n\n## Pure Javascript\n\nWrap the form controls in a `\u003creactive-form\u003e` tag. Every control must have the `data-form-control` attribute set to the control name on the input itself or in a parent element:\n\n```html\n\u003creactive-form id=\"reactiveForm\"\u003e\n    \u003cdiv data-form-control=\"colorInput\"\u003e\n        \u003clabel\u003eColor\u003c/label\u003e\n        \u003cinput type=\"color\" /\u003e\n    \u003c/div\u003e\n\n    \u003cdiv\u003e\n        \u003clabel\u003eText\u003c/label\u003e\n        \u003cinput type=\"text\" data-form-control=\"textInput\" /\u003e\n    \u003c/div\u003e\n\u003c/reactive-form\u003e\n```\n\nIt is also possible to change the attribute name:\n\n```html\n\u003creactive-form id=\"reactiveForm\" data-attribute-name=\"rf-ctrl\"\u003e\n    \u003clabel\u003eText\u003c/label\u003e\n    \u003cinput type=\"text\" rf-ctrl=\"textInput\" /\u003e\n\u003c/reactive-form\u003e\n```\n\nWait for the library to be available and then bind the form group object to the form:\n\n```js\n\nfunction bindForm() {\n    const fg = new FormBuilder().group({\n        // controlName: [Default value, [Sync validators], [Async validators]]\n        textInput: ['default text at initializer', Validators.required],\n    });\n    fg.valueChanges.subscribe(v =\u003e console.log(v));\n    reactiveForm.dataFormGroup = fg;\n}\n\n// As the script is being load asyncronous, wait for FormGroup to be available\nconst itrvl = setInterval(() =\u003e {\n    if (window['FormGroup']) {\n        clearInterval(itrvl);\n        bindForm();\n    }\n}, 100);\n```\n\n## Events\n\n- `valueChanges`:\n\nEach time the value changes, the `valueChanges` event is fired. It is possible to subscribe to the event to observe the value:\n\n```js\nfg.valueChanges.subscribe(v =\u003e console.log(v));\n```\n\n- `statusChanges`:\n\nIn the same way, each time the form is validated or it's status has changed, the `statusChanges` event is fired:\n\n```js\nfg.statusChanges.subscribe(status =\u003e console.log(status === 'VALID' ? 'Form is valid' : 'Form is not valid'));\n```\n\nWhen used with ionic, two events will be rised every time the value or status changes, one per ionic `ionChange` events and another per javascript `change` events. To avoid this duplicate bouncing, there is an optional `data-debounce-time` attribute which can be configured. The usual value to avoid bouncing in the majority of brosers will be about 100ms, but it depends on many factors like browser, user computer speed, etc.\n\nIt only applies to `onValueChanges` and `onStatusChanges` properties.\n\n```html\n\u003creactive-form data-debounce-time=1500\u003e\n```\n\nSubscriptions events will be thrown immedaitely without debouncing:\n\n```js\nconst fg = new FormBuilder().group({\n    textInput: ['default text at initializer', Validators.required],\n});\nfg.valueChanges.subscribe(v =\u003e console.log(v));\nfg.valueChanges.subscribe(v =\u003e console.log(v));\n```\n\n## Status\n\nEach control maintains different status flags:\n\n- `valid`: `true` when control value is `valid`, `false` otherwise. It's not computed until all validators (sync and async) are computed. Changes to `false` immediately when the value is changed.\n- `invalid`: `true` when the control value is `invalid`, `false` otherwise. Changes to `false` when the value changes and is computed after all validators (sync and async) are computed.\n- `pristine`: `true` when the control value has not been modified by the user. `false` when the value has been changed, even when it is the same as the original.\n- `touched`: `true` when the control value has got the focus at least once. `false` otherwise.\n- `dirty`: `true` when the control value has been modified by the user. `false` otherwise.\n\nThe form itself has the same flags, but they correspond to all the controls in the form. I.e. `valid` is `true` when all the controls have `valid` status, `false` when some of the components has `valid` status to `false`.\n\n## Validators\n\nThere are some predefined validators:\n- `Validators.min`: Validator that requires the control's value to be greater than or equal to the provided number.\n- `Validators.max`: Validator that requires the control's value to be less than or equal to the provided number.\n- `Validators.required`: Validator that requires the control have a non-empty value.\n- `Validators.requiredTrue`: Validator that requires the control's value be true. This validator is commonly used for required checkboxes.\n- `Validators.email`: Validator that requires the control's value pass an email validation test.\n- `Validators.minLength`: Validator that requires the length of the control's value to be greater than or equal to the provided minimum length. This validator is also provided by default if you use the the HTML5 `minlength` attribute. Note that the `minLength` validator is intended to be used only for types that have a numeric `length` property, such as strings or arrays. The `minLength` validator logic is also not invoked for values when their `length` property is 0 (for example in case of an empty string or an empty array), to support optional controls. You can use the standard `required` validator if empty values should not be considered valid.\n- `Validators.maxLength`: Validator that requires the length of the control's value to be less than or equal to the provided maximum length. This validator is also provided by default if you use the the HTML5 `maxlength` attribute. Note that the `maxLength` validator is intended to be used only for types that have a numeric `length` property, such as strings or arrays.\n- `Validators.pattern`: Validator that requires the control's value to match a regex pattern. This validator is also provided by default if you use the HTML5 `pattern` attribute.\n- `Validators.nullValidator`: Validator that performs no operation.\n- `Validators.compose`: Compose multiple validators into a single function that returns the union of the individual error maps for the provided control.\n- `Validators.composeAsync`: Compose multiple async validators into a single function that returns the union of the individual error objects for the provided control.\n\nIt is possible to create custom validators. Errors should be reported in the form `{ 'error description': { additionalInfo: '' } }:\n\n```js\nfunction linealRegression(a, b) {\n    return control =\u003e {\n        const value = parseInt(control.value || 0, 10);\n        const actual = value * a + b;\n        return actual \u003e max ? { 'max square': { max, actual } } : null;\n    };\n}\n\nconst fg = new FormBuilder().group({\n    // controlName: [Default value, [Sync validators], [Async validators]]\n    numberInput: [0, squareMax(144)],\n});\n```\n\n## AsyncValidators\n\nAsync validators allows to perform an async operation and wait for the result to validate the input:\n\n```js\nfunction asyncUrlExits(control) {\n    return new Promise((resolve) =\u003e {\n        const url = control.value;\n        if (url) {\n            fetch(url)\n                .then(() =\u003e resolve(null))\n                .catch(error =\u003e resolve({ 'url error': { url, error } }));\n        } else {\n            resolve({ 'empty url': { url }});\n        }\n    });\n}\n\nconst fg = new FormBuilder().group({\n    // controlName: [Default value, [Sync validators], [Async validators]]\n    numberInput: ['https://bitgenoma.com', [], asyncUrlExits],\n});\n```\n\n# Quick Examples\n\n## Javascript\n\nSee javascript example at [examples folder](examples/javascript.html)\n\n```html\n\u003chtml\u003e\n\u003chead\u003e\n    \u003cscript type=\"module\" src=\"https://unpkg.com/forms-reactive/dist/forms-reactive/forms-reactive.esm.js\"\u003e\u003c/script\u003e\n    \u003cscript nomodule src=\"https://unpkg.com/forms-reactive/dist/forms-reactive/forms-reactive.js\"\u003e\u003c/script\u003e\n\u003c/head\u003e\n\n\u003cbody\u003e\n    \u003creactive-form id=\"reactiveForm\"\u003e\n        \u003cdiv data-form-control=\"textInput\"\u003e\n            \u003clabel for=\"textInput\"\u003eText input\u003c/label\u003e\n            \u003cinput type=\"text\" name=\"textInput\" /\u003e\n        \u003c/div\u003e\n    \u003c/reactive-form\u003e\n\n    \u003cpre id=\"liveFormValue\"\u003e\u003c/pre\u003e\n\n    \u003cscript\u003e\n        const reactiveForm = document.getElementById('reactiveForm');\n        const liveFormValue = document.getElementById('liveFormValue');\n\n        function updateLive(value) {\n            liveFormValue.innerHTML = JSON.stringify(value, undefined, 4);\n        }\n\n        /**\n         * Helper function that will wait until class is available\n         */\n        function whenAvailable(cls, cb) {\n            if (typeof cls === 'string') {\n                cls = [cls];\n            }\n            window.setTimeout(() =\u003e cls.every(c =\u003e window[c]) ? cb(window[cls]) : whenAvailable(cls, cb), 10);\n        }\n\n        // Need to wait to all components are loaded\n        whenAvailable('FormBuilder', () =\u003e {\n            const fg = new FormBuilder().group({\n                textInput: ['default text at initializer', Validators.required],\n            });\n            fg.valueChanges.subscribe(v =\u003e updateLive(v));\n            reactiveForm.dataFormGroup = fg;\n            updateLive(fg.value);\n        });\n    \u003c/script\u003e\n\u003c/body\u003e\n\n\u003c/html\u003e\n```\n## Stencil.js\n\nSee complete stencil example at [src folder](src/components/test-component.tsx)\n\n```tsx\nimport { Component, h, State } from '@stencil/core';\nimport { FormBuilder, FormGroup, Validators } from 'forms-reactive';\n\n@Component({\n    tag: 'test-component',\n    styleUrl: 'test-component.css',\n})\nexport class TestComponent {\n    dataFormGroup: FormGroup;\n    subscription;\n\n    componentWillLoad() {\n        this.dataFormGroup = new FormBuilder().group({\n            textInputEmpty: ['', Validators.required],\n            textInput: ['default text at initializer', Validators.required],\n            textInputPatched: ['', Validators.required],\n        });\n        this.dataFormGroup.patchValue({ textInputPatched: 'patched value', selectPickerInputPatched: 'Option 3' });\n        this.subscription = this.dataFormGroup.valueChanges.subscribe(value =\u003e console.log(value));\n    }\n\n    disconnectedCallback() {\n        this.subscription.unsubscribe();\n    }\n\n    renderChips(el: any) {\n        return [\n            \u003cion-chip color={el?.valid ? \"success\" : \"danger\"} mode=\"ios\" outline={el?.valid}\u003e\n                \u003cion-label\u003evalid: {el?.valid ? \"true\" : \"false\"}\u003c/ion-label\u003e\n            \u003c/ion-chip\u003e,\n            \u003cion-chip color={el?.invalid ? \"danger\" : \"success\"} mode=\"ios\" outline={el?.invalid}\u003e\n                \u003cion-label\u003einvalid: {el?.invalid ? \"true\" : \"false\"}\u003c/ion-label\u003e\n            \u003c/ion-chip\u003e,\n            \u003cion-chip color={el?.pristine ? \"primary\" : \"secondary\"} mode=\"ios\" outline={el?.pristine}\u003e\n                \u003cion-label\u003epristine: {el?.pristine ? \"true\" : \"false\"}\u003c/ion-label\u003e\n            \u003c/ion-chip\u003e,\n            \u003cion-chip color={el?.touched ? \"primary\" : \"secondary\"} mode=\"ios\" outline={el?.touched}\u003e\n                \u003cion-label\u003etouched: {el?.touched ? \"true\" : \"false\"}\u003c/ion-label\u003e\n            \u003c/ion-chip\u003e,\n            \u003cion-chip color={el?.dirty ? \"primary\" : \"secondary\"} mode=\"ios\" outline={el?.dirty}\u003e\n                \u003cion-label\u003edirty: {el?.dirty ? \"true\" : \"false\"}\u003c/ion-label\u003e\n            \u003c/ion-chip\u003e,\n        ];\n    }\n\n    render() {\n        return \u003creactive-form dataFormGroup={this.dataFormGroup}\u003e\n            \u003cion-item lines=\"none\"\u003e\n                \u003cion-label position=\"stacked\"\u003eEmpty Text input\u003c/ion-label\u003e\n                \u003cion-input type=\"text\" data-form-control=\"textInputEmpty\"\u003e\u003c/ion-input\u003e\n                \u003cion-note\u003e\n                    Errors: {Object.keys(this.dataFormGroup?.controls['textInputEmpty'].errors || { none: true }).map(k =\u003e `${k}: ${this.dataFormGroup?.controls['textInputEmpty'].getError(k)}`)}\n                \u003c/ion-note\u003e\n            \u003c/ion-item\u003e\n            \u003cion-item lines=\"full\"\u003e{this.renderChips(this.dataFormGroup?.controls['textInputEmpty'])}\u003c/ion-item\u003e\n\n            \u003cion-item lines=\"none\"\u003e\n                \u003cion-label position=\"stacked\"\u003eText input with value on constructor\u003c/ion-label\u003e\n                \u003cion-input type=\"text\" data-form-control=\"textInput\"\u003e\u003c/ion-input\u003e\n                \u003cion-note\u003e\n                    Errors: {Object.keys(this.dataFormGroup?.controls['textInput'].errors || { none: true }).map(k =\u003e `${k}: ${this.dataFormGroup?.controls['textInput'].getError(k)}`)}\n                \u003c/ion-note\u003e\n            \u003c/ion-item\u003e\n            \u003cion-item lines=\"full\"\u003e{this.renderChips(this.dataFormGroup?.controls['textInput'])}\u003c/ion-item\u003e\n\n            \u003cion-item lines=\"none\"\u003e\n                \u003cion-label position=\"stacked\"\u003eText input patched value\u003c/ion-label\u003e\n                \u003cion-input type=\"text\" data-form-control=\"textInputPatched\"\u003e\u003c/ion-input\u003e\n                \u003cion-note\u003e\n                    Errors: {Object.keys(this.dataFormGroup?.controls['textInputPatched'].errors || { none: true }).map(k =\u003e `${k}: ${this.dataFormGroup?.controls['textInputPatched'].getError(k)}`)}\n                \u003c/ion-note\u003e\n            \u003c/ion-item\u003e\n            \u003cion-item lines=\"full\"\u003e{this.renderChips(this.dataFormGroup?.controls['textInputPatched'])}\u003c/ion-item\u003e\n        \u003c/reactive-form\u003e;\n    }\n}\n```\n\n# RoadMap\n\n[ ] remove rxjs dependency\n[ ] add typings for typescript, so we can validate .patch and .set with formGroup definition\n[ ] remove test-component compilation at build time\n[ ] test/verify validators behaviour\n\n# License\n\n```LICENSE\nMIT License\n\nCopyright AppFeel (Bit Genoma Digital Solutions SL) All Rights Reserved.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fappfeel%2Freactive-forms","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fappfeel%2Freactive-forms","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fappfeel%2Freactive-forms/lists"}