{"id":18327609,"url":"https://github.com/angular-redux/form","last_synced_at":"2025-04-06T01:32:05.681Z","repository":{"id":51267706,"uuid":"77984115","full_name":"angular-redux/form","owner":"angular-redux","description":"Keep your Angular2+ form state in Redux","archived":false,"fork":false,"pushed_at":"2018-07-13T20:26:14.000Z","size":375,"stargazers_count":41,"open_issues_count":14,"forks_count":15,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-03-21T15:10:58.136Z","etag":null,"topics":["angular","redux","redux-state"],"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/angular-redux.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-01-04T05:31:06.000Z","updated_at":"2022-12-02T14:29:30.000Z","dependencies_parsed_at":"2022-09-05T21:40:18.049Z","dependency_job_id":null,"html_url":"https://github.com/angular-redux/form","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/angular-redux%2Fform","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/angular-redux%2Fform/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/angular-redux%2Fform/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/angular-redux%2Fform/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/angular-redux","download_url":"https://codeload.github.com/angular-redux/form/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247423475,"owners_count":20936621,"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","redux","redux-state"],"created_at":"2024-11-05T19:11:36.107Z","updated_at":"2025-04-06T01:32:04.572Z","avatar_url":"https://github.com/angular-redux.png","language":"TypeScript","readme":"# ****REPO DEPRECATED****\n\nPlease note that this repo has been deprecated. Code and issues are being migrated to a monorepo at https://github.com/angular-redux/platform where we are beginning work on a new and improved v10. To file any new issues or see the state of the current code base, we would love to see you there! Thanks for your support!\n\n## @angular-redux/form\n\n[![Join the chat at https://gitter.im/angular-redux/ng2-redux](https://badges.gitter.im/angular-redux/ng2-redux.svg)](https://gitter.im/angular-redux/ng2-redux?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge\u0026utm_content=badge)\n[![npm version](https://img.shields.io/npm/v/@angular-redux/form.svg)](https://www.npmjs.com/package/@angular-redux/form)\n[![downloads per month](https://img.shields.io/npm/dm/@angular-redux/form.svg)](https://www.npmjs.com/package/@angular-redux/form)\n\nThis library is a thin layer of connective tissue between Angular 2+ forms and\nRedux. It provides unidirectional data binding between your Redux state and\nyour forms elements. It builds on existing Angular functionality like\n[NgModel](https://angular.io/docs/ts/latest/api/forms/index/NgModel-directive.html)\nand\n[NgControl](https://angular.io/docs/ts/latest/api/forms/index/NgControl-class.html)\n\nThis supports both [Template driven forms](https://angular.io/guide/forms) and [Reactive driven forms](https://angular.io/guide/reactive-forms).\n\n#### Template Driven\n\nFor the simplest use-cases, the API is very straightforward. Your template\nwould look something like this:\n\n```html\n  \u003cform connect=\"myForm\"\u003e\n    \u003cinput type=\"text\" name=\"address\" ngControl ngModel /\u003e\n  \u003c/form\u003e\n```\n\nThe important bit to note here is the `[connect]` directive. This is the only thing\nyou should have to add to your form template in order to bind it to your Redux state.\nThe argument provided to `connect` is basically a path to form state inside of your\noverall app state. So for example if my Redux app state looks like this:\n\n```json\n{\n  \"foo\": \"bar\",\n  \"myForm\": {\n    \"address\": \"1 Foo St.\"\n  }\n}\n```\n\nThen I would supply `myForm` as the argument to `[connect]`. If myForm were nested\ndeeper inside of the app state, you could do something like this:\n\n```html\n\u003cform [connect]=\"['personalInfo', 'myForm']\"\u003e\n  ...\n\u003c/form\u003e\n```\n\nNote that ImmutableJS integration is provided seamlessly. If `personalInfo` is an\nimmutable Map structure, the library will automatically use `get()` or `getIn()` to\nfind the appropriate bits of state.\n\nThen, in your application bootstrap code, you need to add a provider for\nthe class that is responsible for connecting your forms to your Redux state.\nThere are two ways of doing this: either using an `Redux.Store\u003cT\u003e` object or\nan `NgRedux\u003cT\u003e` object. There are no substantial differences between these\napproaches, but if you are already using\n[@angular-redux/store](https://github.com/angular-redux/store) or you wish to integrate\nit into your project, then you would do something like this:\n\n```typescript\nimport { NgReduxModule } from '@angular-redux/store';\nimport { NgReduxFormModule } from '@angular-redux/form';\n\n@NgModule({\n  imports: [\n    BrowserModule,\n    ReactiveFormsModule,\n    FormsModule,\n    NgReduxFormModule,\n    NgReduxModule,\n  ],\n  bootstrap: [MyApplicationComponent]\n})\nexport class ExampleModule {}\n```\n\nOr if you are using Redux without `@angular-redux/store`, then your bootstrap call would look\nmore like this (substitute your own store creation code):\n\n```typescript\nimport {provideReduxForms} from '@angular-redux/form';\n\nconst storeCreator = compose(applyMiddleware(logger))(createStore);\nconst store = create(reducers, \u003cMyApplicationState\u003e {});\n\n@NgModule({\n  imports: [\n    BrowserModule,\n    ReactiveFormsModule,\n    FormsModule,\n    NgReduxFormModule,\n  ],\n  providers: [\n    provideReduxForms(store),\n  ],\n  bootstrap: [MyApplicationComponent]\n})\nexport class ExampleModule {}\n```\n\nThe essential bit of code in the above samples is the call to `provideReduxForms(...)`.\nThis configures `@angular-redux/form` and provides access to your Redux store or NgRedux\ninstance. The shape of the object that `provideReduxForms` expects is very\nbasic:\n\n```typescript\nexport interface AbstractStore\u003cRootState\u003e {\n  /// Dispatch an action\n  dispatch(action: Action \u0026 {payload?}): void;\n\n  /// Retrieve the current application state\n  getState(): RootState;\n\n  /// Subscribe to changes in the store\n  subscribe(fn: () =\u003e void): Redux.Unsubscribe;\n}\n```\n\nBoth `NgRedux\u003cT\u003e` and `Redux.Store\u003cT\u003e` conform to this shape. If you have a more\ncomplicated use-case that is not covered here, you could even create your own store\nshim as long as it conforms to the shape of `AbstractStore\u003cRootState\u003e`.\n\n### How the bindings work\n\nThe bindings work by inspecting the shape of your form and then binding to a Redux\nstate object that has the same shape. The important element is `NgControl::path`.\nEach control in an Angular 2 form has a computed property called `path` which uses\na very basic algorithm, ascending the tree from the leaf (control) to the root\n(the `\u003cform\u003e` element) and returning an array containing the name of each group or\narray in the path. So for example, let us take a look at this form that lets the\nuser provide their full name and the names and types of their children:\n\n```html\n\u003cform connect=\"form1\"\u003e\n  \u003cinput ngControl ngModel name=\"fullname\" type=\"text\" /\u003e\n  \u003ctemplate connectArray let-index connectArrayOf=\"dependents\"\u003e\n    \u003cdiv [ngModelGroup]=\"index\"\u003e\n      \u003cinput ngControl ngModel name=\"fullname\" type=\"text\" /\u003e\n      \u003cselect ngControl ngModel name=\"type\"\u003e\n        \u003coption value=\"adopted\"\u003eAdopted\u003c/option\u003e\n        \u003coption value=\"biological\"\u003eBiological child\u003c/option\u003e\n      \u003c/select\u003e\n    \u003c/div\u003e\n  \u003c/template\u003e\n\u003c/form\u003e\n```\n\nOur root `\u003cform\u003e` element has a `connect` directive that points to the state element\n`form1`. This means that the children within your form will all be bound to some\nbit of state inside of the `form1` object in your Redux state. Then we have a child\ninput which is bound to a property called `fullname`. This is a basic text box. If\nyou were to inspect it in the debugger, it would have a `path` value like this:\n\n```\n['form1', 'fullname']\n```\n\nAnd therefore it would bind to this piece of Redux state:\n\n```json\n{\n  \"form1\": {\n    \"fullname\": \"Chris Bond\"\n  }\n}\n```\n\nSo far so good. But look at the array element inside our form, in the `\u003ctemplate\u003e`\nelement. It is bound to an array property called `dependents`. The elements inside\nof the `\u003ctemplate\u003e` tag contain the template that will be instantiated for each\nelement inside of the `dependents` array. The `ngModelGroup` specifies that we should\ncreate a `FormGroup` element for each item in the array and the name of that group\nshould be the value of `index` (the zero-based index of the element that is being\nrendered). This is important because it allows us to create a form structure that\nmatches our Redux state. Let's say our state looks like this:\n\n```json\n{\n  \"form1\": {\n    \"fullname\": \"Chris Bond\",\n    \"dependents\": [\n      {\n        \"fullname\": \"Christopher Bond Jr.\",\n        \"type\": \"biological\"\n      }\n    ]\n  }\n}\n```\n\nIf you think about the 'path' to the first element of the dependents array, it would\nbe this:\n\n```\n['form1', 'dependents', 0]\n```\n\nThe last element, `0`, is the index into the `dependents` array. This is our\n`ngModelGroup` element. This allows us to create a form structure that has the\nsame structure as our Redux state. Therefore if we pause the debugger and look at\nthe `path` property on our first `\u003cselect\u003e` element, it would look like this:\n\n```\n['form1', 'dependents', 0, 'type']\n```\n\nFrom there, `@angular-redux/form` is able to take that path and extract the value for\nthat element from the Redux state.\n\n#### Reactive Forms\nThe value in \"connect\" attribute is the value that will show up in the Redux store. The formGroup value is the name of the object in your code that represents the form group.\n\n```html\n  \u003cform connect=\"myForm\" [formGroup]=\"loginForm\"\u003e\n    \u003cinput type=\"text\" name=\"address\" formControlName=\"firstName\" /\u003e\n  \u003c/form\u003e\n```\n\n#### Troubleshooting\n\nIf you are having trouble getting data-binding to work for an element of your form,\nit is almost certainly because the `path` property on your control does not match\nthe structure of your Redux state. Try pausing the debugger in `Connect::resetState`\nand check the value of `path` on the control that has failed to bind. Then make sure\nit is a valid path to the state in question.\n\n### Reducers\n\nThe library will automatically bind your state to value of your form inputs. This is\nthe easy part and is unlikely to cause any problems for you. Slightly more difficult\nis _updating your Redux state_ when the form values change. There are two approaches\nthat you can take in order to do this.\n\nThe first, and by far the simplest, is to use the reducer that comes with `@angular-redux/form`\nand uses the value supplied in `connect` and the form input names in order to update\nyour Redux state automatically. If you do not need to do any special processing on\nyour data when the user updates form inputs, then you should use this default reducer.\nTo use it, you need to combine it with your existing reducers like so:\n\n```typescript\nimport {composeReducers, defaultFormReducer} from '@angular-redux/form';\n\nconst reducer = composeReducers(\n  defaultFormReducer(),\n  combineReducers({\n    foo: fooReducer,\n    bar: barReducer\n  })\n);\n```\n\nThe important bits of code here are the calls to `composeReducers` and `defaultFormReducer`.\nThe call to `composeReducers` essentially takes your existing reducer configuration and\nchains them together with `defaultFormReducer`. The default form reducer only handles one\naction, `{FORM_CHANGED}`. You can think of it like so:\n\n```typescript\nfunction defaultFormReducer(state, action: Redux.Action \u0026 {payload?}) {\n  switch (action.type) {\n    case FORM_CHANGED:\n      [return new state with form values from action.payload];\n    default:\n      break;\n  }\n  return state;\n}\n```\n\nIf you have a more complex use-case that the default form reducer is incompatible with,\nthen you can very easily just handle the FORM_CHANGED actions in your existing reducers\nand manually update your state with the form values from `action.payload.value`, which\nhas the shape of an object containing all of your raw form values:\n\n```json\n{\n  \"address1\": \"129 Spadina Ave\",\n  \"address2\": \"Toronto, Ontario M4Y 1F7\",\n  \"otherGroup\": {\n    \"foo\": \"bar\",\n    \"biz\": 1\n  }\n}\n```\n\nThis would match a form that looks like this:\n\n```html\n\u003cform connect\u003e\n  \u003cinput name=\"address1\" ngControl ngModel type=\"text\" /\u003e\n  \u003cinput name=\"address2\" ngControl ngModel type=\"text\" /\u003e\n  \u003cform name=\"otherGroup\"\u003e\n    \u003cinput name=\"foo\" ngControl ngModel type=\"text\" /\u003e\n    \u003cinput name=\"biz\" ngControl ngModel type=\"number\" /\u003e\n  \u003c/form\u003e\n\u003c/form\u003e\n```\n\nNote: If you implement your own reducer instead of using the default one provided by\nng2-form-redux, the state you return still needs to match the shape of your form,\notherwise data-binding is not going to work. This is why it probably makes sense to\njust use the default reducer in almost every case - because your custom reducer would\nhave to implement the same logic and produce a state object that is the same shape.\nBut if you are having trouble with the default reducer, or if you find the fact that\nyou have to use `composeReducers` distasteful, then this is another route available\nto you.\n\nThe unit tests in `*.test.ts` files also contain useful examples of how to build\nforms using `@angular-redux/form`.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fangular-redux%2Fform","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fangular-redux%2Fform","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fangular-redux%2Fform/lists"}