@skynet-ng/dynamic-form
v1.0.1
Published
Angular dynamic form
Downloads
25
Maintainers
Readme
@skynet-ng/dynamic-form
An extension for Angular Reactive forms to render forms dynamically.
About
Reactive forms compatible dynamic form called upon to facilitate work with form, render a form from a config (from JSON, from Back-end response, etc). Works with ControlValueAccessor powered components. Supports all the functionality owned by Angular Reactive Forms.
Showcase
Get started
Step 1: Install the lib.
npm install @skynet-ng/dynamic-form
Step 2: Import DynamicFormModule into your module
import { NgModule } from '@angular/core';
import { DynamicFormModule } from '@skynet-ng/dynamic-form';
@NgModule({
imports: [
...
DynamicFormModule
],
...
})
export class SomeModule {}
Step 3: Create ControlValueAccessor component. Example of such a component is here. Let's implement TextfieldComponent.
/* eslint-disable @typescript-eslint/no-empty-function */
import { Component, forwardRef, Input } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
@Component({
selector: 'textfield-control',
template: `
<div class="flex flex-col gap-1">
<label class="block text-sm font-medium text-gray-700">{{ label }}</label>
<input
class="block w-full border border-gray-300 rounded-md sm:text-sm shadow-sm outline-indigo-500 p-3"
[disabled]="isDisabled"
[type]="type"
[(ngModel)]="value"
[placeholder]="placeholder"
[disabled]="isDisabled"
/>
</div>
`,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TextfieldComponent),
multi: true,
},
],
})
export class TextfieldComponent implements ControlValueAccessor {
private _value: string;
get value(): any {
return this._value;
}
set value(value: any) {
if (value !== this._value) {
this._value = value;
this.onChange(value);
}
}
isDisabled: boolean;
@Input()
label: string;
@Input()
placeholder = '';
@Input()
type: 'text' | 'number' = 'text';
onChange = (_) => {};
onTouch = () => {};
writeValue(value: any): void {
this._value = value;
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouch = fn;
}
setDisabledState(isDisabled: boolean) {
this.isDisabled = isDisabled;
}
}
Step 4:
In your component's ts file you want to implement form in, define form via DynamicFormGroup. After dynamic form is defined, pass it through dynamicFormGroup
input in dynamic-form-outlet
component.
import { Component } from '@angular/core';
import { DynamicControl, DynamicFormGroup } from '@skynet-ng/dynamic-form';
import { TextfieldComponent } from './components';
@Component({
selector: 'example-app-root',
template: `
<div class="flex justify-center items-center absolute w-full h-full bg-gray-200">
<div class="bg-white flex flex-col w-96 border-gray-300 border border-solid p-5 rounded-md gap-4">
<form class="flex flex-col gap-4">
<dynamic-form-outlet [dynamicFormGroup]="dynamicFormGroup"></dynamic-form-outlet>
</form>
<button class="self-end h-10 inline-flex justify-center rounded-md border border-transparent bg-indigo-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-indigo-700">
Submit
</button>
</div>
</div>
`
})
export class AppComponent {
dynamicFormGroup = new DynamicFormGroup({
name: new DynamicControl({
initialInputs: {
label: 'Name',
placeholder: 'Enter your name'
}
}, TextfieldComponent),
surname: new DynamicControl({
initialInputs: {
label: 'Surname',
placeholder: 'Enter your surname'
}
}, TextfieldComponent),
age: new DynamicControl({
initialInputs: {
label: 'Age',
placeholder: 'Enter your age',
type: 'number'
}
}, TextfieldComponent)
});
}
You can play with it in StackBlitz. Also, this example is availabe in GitHub.
Usage notes
- Controls are being rendered outside
dynamic-form-outlet
element. This is done this way in order to allow outer element to apply layout styles for controls.dynamic-form-outlet
element gets removed from DOM so it does not participate in layout. See screenshot from the dev tools:
- The rendering system adds ids for each control. It takes the name of a control which it's defined in
DynamicFormGroup
with (how it looks like in dev tools can bee seen on the screenshot from the previous bullet). This part of functionality is needed for automation tests so that they can select specific control HTML elements. Example of names the rendering system use:
- The rendering system puts
dynamic-control
empty attribute for each being rendered controls. Might be useful for selection purposes.