{"id":20218538,"url":"https://github.com/danielcornock/ngx-power-forms","last_synced_at":"2026-05-10T01:24:12.656Z","repository":{"id":100014104,"uuid":"354003341","full_name":"danielcornock/ngx-power-forms","owner":"danielcornock","description":"☑️ Angular forms library to abstract the boilerplate.","archived":false,"fork":false,"pushed_at":"2021-04-14T19:56:53.000Z","size":596,"stargazers_count":1,"open_issues_count":3,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-02-26T03:34:54.057Z","etag":null,"topics":["angular","forms","library","reactive-forms"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/danielcornock.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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-04-02T11:49:46.000Z","updated_at":"2022-07-02T01:57:41.000Z","dependencies_parsed_at":"2023-06-25T21:10:04.434Z","dependency_job_id":null,"html_url":"https://github.com/danielcornock/ngx-power-forms","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielcornock%2Fngx-power-forms","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielcornock%2Fngx-power-forms/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielcornock%2Fngx-power-forms/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/danielcornock%2Fngx-power-forms/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/danielcornock","download_url":"https://codeload.github.com/danielcornock/ngx-power-forms/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241660784,"owners_count":19998942,"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","library","reactive-forms"],"created_at":"2024-11-14T06:38:58.402Z","updated_at":"2026-05-10T01:24:12.606Z","avatar_url":"https://github.com/danielcornock.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Ngx Power Forms\n\nA library designed to abstract away the repetitive elements of building a form. Takes away a small amount of the flexibility provided by Angular forms, but saves you time in the process. The library is built on top of Angular reactive forms and makes extensive use of observables, to allow for `onPush` change detection.\n\nDemo is available [here](https://ngx-power-forms.netlify.app/).\n\n## Installation and setup\n\nTo install the library, use\n\n```\nnpm i ngx-power-forms\n```\n\nOnce installed, you should add the `NgxPowerFormsModule` using `forRoot` to a core module. Within `forRoot` you can provide an object to further customise things such as form validation messages, custom components and miscellanious settings.\n\n```ts\n@NgModule({\n  declarations: [\n    AppComponent\n  ],\n  imports: [\n    BrowserModule,\n    NgxPowerFormsModule.forRoot({\n      customErrors: {\n        required: () =\u003e 'Please ensure you fill in this field'\n      },\n      customOptions: {\n        showRequiredSymbol: false\n      }\n    })\n  ],\n  providers: [],\n  bootstrap: [AppComponent]\n})\nexport class AppModule {}\n```\n\nTo access the styles for the project, you should install the stylesheet in to your `angular.json` file.\n\n```json\n\"architect\": {\n  \"build\": {\n    \"options\": {\n      \"styles\": [\n        \"node_modules/ngx-power-forms/src/lib/styles/index.scss\",\n      ]\n    }\n  }\n}\n```\n\n## Creating a form\n\nTo create your first form, you can use the `FormFactory` service.\n\nBelow you can find an extensive example of a range of inputs that are available using the service.\n\n```ts\n@Component({\n  selector: 'app-root',\n  templateUrl: './app.component.html',\n  styleUrls: ['./app.component.scss']\n})\nexport class AppComponent implements OnInit {\n  public form: FormContainer;\n  public fields: Record\u003cstring, FormInputField\u003e;\n\n  constructor(private formFactory: FormFactory){}\n\n  ngOnInit(): void {\n    this.form = this.formFactory.create({\n      fields: [\n        {\n          name: 'textField',\n          label: 'Text field',\n          type: FormInputType.TEXT,\n          value: 'Starting value',\n          placeholder: 'Placeholder text',\n          validators: [Validators.required],\n          disabled: true\n        },\n        {\n          name: 'selectField',\n          label: 'Select field with placeholder',\n          type: FormInputType.SELECT,\n          placeholder: 'Select your option',\n          value: 1,\n          customConfig: {\n            options: [\n              { label: 'Option 1', value: 1 },\n              { label: 'Option 2', value: 2 }\n            ]\n          }\n        },\n        {\n          name: 'selectReactiveField',\n          label: 'Reactive select field',\n          type: FormInputType.SELECT,\n          customConfig: {\n            options: of([\n              { label: 'Option 1', value: 1 },\n              { label: 'Option 2', value: 2 }\n            ])\n          }\n        },\n        {\n          name: 'numberField',\n          label: 'Number field',\n          type: FormInputType.NUMBER,\n          validators: [Validators.min(5)]\n        },\n        {\n          name: 'checkboxField',\n          label: 'Checkbox field',\n          type: FormInputType.CHECKBOX,\n          customConfig: {}\n        },\n        {\n          name: 'radioField',\n          label: 'Radio field',\n          type: FormInputType.RADIO,\n          customConfig: {\n            options: [\n              { label: 'Option 1', value: 1 },\n              { label: 'Option 2', value: 2 }\n            ]\n          }\n        },\n        {\n          name: 'textareaField',\n          label: 'Text area field',\n          type: FormInputType.TEXTAREA,\n          validators: [Validators.minLength(20), Validators.maxLength(100)]\n        },\n        {\n          name: 'multiSelectField',\n          label: 'Multi select field',\n          type: FormInputType.MULTI_SELECT,\n          placeholder: 'Select your items',\n          value: [1],\n          customConfig: {\n            options: [\n              { label: 'Option 1', value: 1 },\n              { label: 'Option 2', value: 2 }\n            ]\n          }\n        },\n        {\n          name: 'dateField',\n          label: 'Date field',\n          type: FormInputType.DATETIME\n        },\n                {\n          name: 'customSelect',\n          label: 'Custom select',\n          type: FormInputType.CUSTOM_SELECT,\n          value: 1,\n          customConfig: {\n            options: [{ label: 'Hello', value: 1 }, { label: 'Yo!', value: 2 }],\n            component: CustomSelectOptionComponent\n          }\n        },\n        {\n          name: 'customMultiSelect',\n          label: 'Custom multi select',\n          type: FormInputType.CUSTOM_MULTI_SELECT,\n          value: [],\n          customConfig: {\n            options: [\n              { label: 'Option 1', value: 1 },\n              { label: 'Option 2', value: 2 },\n              { label: 'Option 3', value: 3 }\n            ]\n          }\n        },\n        {\n          name: 'resultTextField',\n          label: 'This is disabled when the previous checkbox is not checked',\n          type: FormInputType.TEXT,\n          hooks: {\n            onInit: (field) =\u003e {\n              const decidingCheckbox = this.form.getField('checkboxField');\n\n              if (decidingCheckbox) {\n                decidingCheckbox.value$.pipe(startWith(decidingCheckbox.value)).subscribe((val) =\u003e {\n                  field.setDisabled(!val);\n                });\n              }\n            }\n          }\n        }\n      ],\n      onSave: (formValue) =\u003e console.log(formValue)\n    });\n\n    this.fields = this.form.getFieldsObject();\n  }\n}\n\n```\n\nTo then use these fields in your template, it looks as simple as this:\n\n```html\n\u003cform [formGroup]=\"form.formGroup\" (ngSubmit)=\"form.save()\"\u003e\n  \u003cpow-form-input [formInputField]=\"fields.textField\"\u003e\u003c/pow-form-input\u003e\n  \u003cpow-form-input [formInputField]=\"fields.dateField\"\u003e\u003c/pow-form-input\u003e\n  \u003cpow-form-input-select [formInputField]=\"fields.selectField\"\u003e\u003c/pow-form-input-select\u003e\n  \u003cpow-form-input-select [formInputField]=\"fields.selectReactiveField\"\u003e\u003c/pow-form-input-select\u003e\n  \u003cpow-form-input-multi-select [formInputField]=\"fields.multiSelectField\"\u003e\u003c/pow-form-input-multi-select\u003e\n  \u003cpow-form-input-number [formInputField]=\"fields.numberField\"\u003e\u003c/pow-form-input-number\u003e\n  \u003cpow-form-input-checkbox [formInputField]=\"fields.checkboxField\"\u003e\u003c/pow-form-input-checkbox\u003e\n  \u003cpow-form-input-radio-set [formInputField]=\"fields.radioField\"\u003e\u003c/pow-form-input-radio-set\u003e\n  \u003cpow-form-input-textarea [formInputField]=\"fields.textareaField\"\u003e\u003c/pow-form-input-textarea\u003e\n\u003c/form\u003e\n```\n\nAlternatively, if you don't need anything in between the form inputs, you can use `pow-form-input-item` to loop through your fields in the order that they were added to the array.\n\n```html\n\u003cform [formGroup]=\"form.formGroup\" (ngSubmit)=\"form.save()\"\u003e\n  \u003cpow-form-input-item *ngFor=\"let field of form.fields\" [formInputField]=\"field\"\u003e\u003c/pow-form-input-item\u003e\n\u003c/form\u003e\n```\n\nTo simplify this even more, we've also added a `pow-form` component, where you simply pass in the `formContainer` instance as an input and then embed your action buttons inside the element.\n\n\u003e If one of your buttons is of type 'submit', the submission of the form will be automatic and the function that to passed to the `onSave` field of the config object will be called.\n\n```html\n\u003cpow-form [formContainer]=\"form\"\u003e\n  \u003cinput type=\"submit\" value=\"Submit form\"\u003e\n\u003c/pow-form\u003e\n```\n\n## Typings \u0026 interfaces\n\nTypings vary between each form input type. By assigning a value to the `type` field using the `FormInputType` enum, TypeScript is able to determine the intellisense needed, for example requiring `options` in the `customConfig` for select and radio fields.\n\n| Field           \t| Description                                                                                                                                                                                                      \t|\n|-----------------\t|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\t|\n| name            \t| The key used for the form group. When getting the value of a containing form, this is the key that the value will be assigned to.                                                                                \t|\n| label           \t| The label that will be displayed with your form input.                                                                                                                                                           \t|\n| type            \t| The type of the form input. Used to determine which component to display when using a dynamic form, or passed straight in to the input type value (e.g. input[type=email]). Can be configured with custom types. \t|\n| placeholder     \t| Where applicable, the placeholder text to display.                                                                                                                                                               \t|\n| disabled        \t| Whether the field is disabled on initialisation                                                                                                                                                                  \t|\n| value           \t| The starting value of the field.                                                                                                                                                                                 \t|\n| validators      \t| Array of validators - the same format as passing it in to a FormControl.                                                                                                                                         \t|\n| asyncValidators \t| Array of async validators - same as passing in to a FormControl.                                                                                                                                                 \t|\n| customConfig    \t| Varies from input to input. For example, `select` and `radio` fields require a field defining `options` in the custom config object.                                                                             \t|\n| hooks           \t| Hooks to be ran in sync with the Angular lifecycle hooks of the component generated. Can pass in an `onInit` hook to configure things like a disabled state of the field depending on the value of other inputs. \t|\n\n\n## Customisability\n\nAs well as being able to override the classes yourself, the project extensively uses CSS variables. This aims to make the forms as customisable as possible without having to manually override CSS classes, with `!important` sprinkled everywhere.\n\n```scss\n  /* Core */\n  --form-input-feature-color: dodgerblue;\n  --form-input-error-color: red;\n\n  /* Form input label */\n  --form-input-label-font-size: 14px;\n  --form-input-label-margin-bottom: 8px;\n  --form-input-label-font-weight: 500;\n  --form-input-label-letter-spacing: 0.5px;\n  --form-input-label-color: grey;\n\n  /* Form input label error state */\n  --form-input-label-color--error: var(--form-input-error-color);\n\n  /* Form input field */\n  --form-input-background-color: white;\n  --form-input-vertical-padding: 12px;\n  --form-input-horizontal-padding: 12px;\n  --form-input-padding: var(--form-input-vertical-padding) var(--form-input-horizontal-padding);\n  --form-input-border-color: grey;\n  --form-input-border: 1px solid var(--form-input-border-color);\n  --form-input-border-radius: 5px;\n  --form-input-font-size: 16px;\n  --form-input-font-color: black;\n  --form-input-placeholder-color: grey;\n\n  /* Form input field disabled states */\n  --form-input-border-style--disabled: dashed;\n  --form-input-opacity--disabled: 0.5;\n\n  /* Form input field focus states */\n  --form-input-outline-width--focus: 1px;\n  --form-input-outline-color--focus: var(--form-input-feature-color);\n\n  /* Form input field error states */\n  --form-input-border-color--error: var(--form-input-error-color);\n  --form-input-outline-color--error: var(--form-input-error-color);\n\n  /* Form input error text */\n  --form-input-error-font-weight: 400;\n  --form-input-error-font-size: 14px;\n  --form-input-error-spacing-top: 4px;\n\n  /* Form input host */\n  --form-input-spacing: 24px;\n\n  /* -- Custom fields -- */\n\n  /* Select */\n  --form-input-select-icon-color: var(--form-input-border-color);\n  --form-input-select-icon-spacing: var(--form-input-horizontal-padding);\n  \n  /* Checkbox */\n  --form-input-checkbox-size: 24px;\n  --form-input-checkbox-spacing: 10px;\n  --form-input-checkbox-label-font-size: var(--form-input-label-font-size);\n  --form-input-checkbox-check-color: var(--form-input-feature-color);\n  --form-input-checkbox-inner-size: 12px;\n  --form-input-checkbox-label-font-size: 12px;\n  --form-input-checkbox-inner-radius: 3px;\n  \n  /* Radio */\n  --form-input-radio-size: 22px;\n  --form-input-radio-selected-size: 11px;\n  --form-input-radio-spacing: 10px;\n  --form-input-radio-label-spacing: 10px;\n  --form-input-radio-border: 1px solid var(--form-input-border-color);\n  --form-input-radio-label-font-size: var(--form-input-checkbox-label-font-size);\n  --form-input-radio-check-color: var(--form-input-checkbox-check-color);\n\n  /* Textarea */\n  --form-input-textarea-line-height: 1.4;\n\n  /* Multi select */\n  --form-input-multi-select-option-background-color: var(--form-input-feature-color);\n  --form-input-multi-select-option-text-color: white;\n  --form-input-multi-select-option-font-size: 14px;\n  --form-input-multi-select-option-vertical-padding: 9px;\n  --form-input-multi-select-option-horizontal-padding: 8px;\n  --form-input-multi-select-option-padding: var(--form-input-multi-select-option-vertical-padding) var(--form-input-multi-select-option-horizontal-padding);\n  --form-input-multi-select-option-radius: var(--form-input-border-radius);\n  --form-input-multi-select-dropdown-border: 1px solid #ddd;\n  --form-input-multi-select-focus-background: #{rgba(black, 0.05)};\n  --form-input-multi-select-dropdown-font-size: 16px;\n  --form-input-multi-select-dropdown-option-padding: 12px;\n```\n\nThe form styles have been designed specifically to try and reduce the amount of customisation that you will have to do. They have also been created with accessibility in mind. You can overwrite the variables by using a `:root` tag in your global stylesheet.\n\nEach form input container will by default have 3 CSS classes assigned to it, `form-input-host`, `form-input-host-${formInputType}`, and `form-input-host-${formInputName}` to allow for easy external styling across the whole app. An example of this is the `--form-input-spacing` variable which dictates the default spacing between form input components when displayed next to each other.\n\n## Custom option components\n\nSome components, such as `FormInputCustomSelect` and `FormInputCustomMultiSelect` can have a component passed in to the custom config when creating the input. This component must extend `CustomSelectOptionComponent`, and then you are free to create a new template and styles to make the custom select option appear however you want.\n\nCreate a clickable element that calls the `onSelect` function in the callback, and use the `isSelected` input to style your custom component when it has been selected.\n\nIf you want to customise the container of the custom select options, simply target `form-input-custom-select`, or `form-input-custom-multi-select` respectively.\n## Creating custom form components\n\nTo integrate your own component in to the framework, there are a few things that you need to set up. The first thing is to extend the libraries types to allow you to define a custom config interface for your component.\n\nFor this example, we will be adding our own 'range' component.\n\nIt is recommended to create a new directory, whether it be in your shared folder or as a new module, named `forms`.\n\n### Extending the types\n\nFirst, lets create a new enum to hold our new form input type.\n\n```ts\nexport enum FormInputCustomType {\n  RANGE = 'range'\n};\n```\n\nThen, create a new interface file. We've called ours `form-input-range-config.interface.ts`.\n\nIn this file, we want to create two interfaces.\n\n```ts\nexport interface FormInputRangeConfig extends FormInputBaseConfig {\n  type: FormInputCustomType.RANGE;\n  customConfig: FormInputRangeCustomConfig;\n  value: number;\n}\n\nexport interface FormInputRangeCustomConfig {\n  min: number;\n  max: number;\n  step: number;\n}\n```\n\nThe first interface defines the object that we will need to pass in to the arguments to our form input creation method. In here you should extend `FormInputBaseConfig`, and override the type with the value of the enum that we have just created. We've also overriden the `value` field, as we want to make it require a `number`.\n\nFinally, if our new component will have any extra config that is not part of a standard input, we want to create a new interface below it and assign that interface to the `customConfig` field on the primary config interface.\n\nFor this example we will require a `min`, `max` and `step` fields in order to configure our slider.\n\nFinally, we need to integrate this interface with the library. Create a new types file in this directory called `power-forms.d.ts`, and paste the following in to the file.\n\n```ts\ndeclare module 'ngx-power-forms' {\n  interface FormInputConfigMap {\n    [FormInputCustomType.RANGE]: FormInputRangeConfig;\n  }\n}\n```\n\nOnce this is done, when you are creating a new input using the `FormInputFactory` or `FormFactory`, if you try to create an input with a `type` of `FormInputCustomField.RANGE`, the intellisense will match the interface to the one you previously created, and typescript will make sure that you provide the correct parameters.\n\n### Creating the component\n\nTo create a custom component that can be integrated in to the framework, first generate an Angular component using the CLI, and extend this component from `FormInputComponent`. You should pass your `customConfig` interface to the extended `FormInputComponent` as a generic, if your component has a `customConfig` object.\n\n```ts\n@Component({\n  selector: 'app-form-input-range',\n  templateUrl: './form-input-range.component.html',\n  styleUrls: ['./form-input-range.component.scss'],\n  changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class FormInputRangeComponent extends FormInputComponent\u003cFormInputRangeCustomConfig\u003e {}\n```\n\nYou are free to build the template however you wish, however it is recommended to try to stick to the following structure for consistency. The example below is an implementation of our custom built slider.\n\n```html\n\u003cdiv class=\"form-input-container\"\u003e\n  \u003cpow-form-input-label [formInputField]=\"formInputField\"\u003e\u003c/pow-form-input-label\u003e\n\n  \u003cinput class=\"form-input-range\"\n    type=\"range\"\n    [name]=\"formInputField.name\"\n    [id]=\"formInputField.name\"\n    [formControl]=\"formInputField.control\"\n    [min]=\"formInputField.customConfig.min\"\n    [max]=\"formInputField.customConfig.max\"\n    [step]=\"formInputField.customConfig.step\"\u003e\n\n  \u003cpow-form-input-error [errors]=\"formInputField.errors$ | async\"\u003e\u003c/pow-form-input-error\u003e\n\u003c/div\u003e\n```\n\nAs you can see in this example, we have referenced fields inside `customConfig`, and the Angular language service knows that `min`, `max` and `step` are all a part of the `customConfig` object for this component because of the generic that we passed through to the base class.\n\nIf your component follows the normal style of an input (i.e. looks like a text box), you should apply the `form-input` class to the input. This will ensure that the styles are consistent with the rest of the inputs. In this case however, the slider field does not follow the usual structure, so we have omitted that CSS class.\n\n### Integrating with the dynamic form\n\nFinally, if we want our custom component to be able to be integrated with the `pow-form` or `pow-form-input-item` components, we need to pass our component in to our library using the `forRoot` method on the library module.\n\n```ts\n@NgModule({\n  declarations: [\n    AppComponent,\n    FormInputRangeComponent\n  ],\n  imports: [\n    BrowserModule,\n    NgxPowerFormsModule.forRoot({\n      customInputComponents: {\n        [FormInputCustomType.RANGE]: FormInputRangeComponent\n      }\n    })\n  ],\n  providers: [],\n  bootstrap: [AppComponent]\n})\nexport class AppModule { }\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielcornock%2Fngx-power-forms","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdanielcornock%2Fngx-power-forms","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdanielcornock%2Fngx-power-forms/lists"}