angular-dynaform is a library for rapid development of model-driven reactive forms for mobile and web
- generic, expressive and extendable form library
- JSON serializable form configuration
- shared forms for mobile and web
- form builder
- observable form values and form value updates using angulars form model as a tree of FormGroups/FormArrays/FormControls
- observable focus changes
- application data model to form data model mapping
- enable/disable or show/hide fields based on conditions defined on related field values
- generic internationalization (labels, placeholders, options (select/radiobutton))
- easily extensible through:
- custom components
- custom validators
- custom actions (triggered by blur/focus/click events) and/or observing value/status changes
NOTE: Asking for any help! Your contribution is highly welcome!
- install the packages:
npm install @angular-dynaform/core --save
npm install @angular-dynaform/material --save
NOTE: please install required peer dependencies if they are not already installed
npm install @angular/common @angular/core @angular/forms rxjs jsep jsonpointerx
- import the form modules in the root NgModule of your application:
@NgModule({
...
imports: [DynamicFormModule.forRoot(), DynamicMaterialFormModule, ...],
...
})
export class AppModule {
}
- in other modules, import only the 'DynamicFormModule' and do not import the UI specific modules (e.g 'DynamicMaterialFormModule'):
@NgModule({
...
imports: [DynamicFormModule],
...
})
export class SharedModule {
}
- implement a form:
@Component({
selector: 'app-basic-form',
template: `
<adf-form [model]="formModel" (adfSubmit)="onSubmit($event)"></adf-form>
`,
})
export class BasicFormComponent {
@ViewChild(DynamicForm) dynaForm!: DynamicForm;
formModel: FormModel;
readonly formConfig: FormConfig = {
id: 'basic',
updateOn: 'change',
options: {
group: [
{
id: 'name',
modelType: ModelType.MODEL_VALUE,
controlType: ControlType.CONTROL_INPUT,
options: { label: 'name', placeholder: 'Enter your name', maxLength: 30, minLength: 4 },
validators: ['required', 'minLength', 'maxLength'],
},
{
id: 'submit',
modelType: ModelType.MODEL_NULL,
controlType: ControlType.CONTROL_BUTTON,
options: { label: 'Submit' },
action: 'submit',
},
],
},
};
name?: string;
constructor(private dynamicFormService: DynamicFormService) {
this.formModel = this.dynamicFormService.createFormModel(this.formConfig);
}
onSubmit(event: Event): void {
this.name = this.formModel.value.name;
}
}
-
create the form config:
- using the Form Builder:
inject
FormBuilder
into your component class. You can then call its method 'createForm' to create an instance of 'FormBuilderForm'
const form = formBuilder.createForm({ id: 'exampleForm', updateOn: 'blur' });
// create divisions:
const persondiv = form.group.addSubset({ id: 'person', controlType: ControlType.CONTROL_DIVISION });
const buttondiv = form.group.addSubset({
id: 'buttondivision',
controlType: [ControlType.CONTROL_DIVISION],
options: { css: { content: 'button-division-content' } },
});
// add controls to the persondiv division:
persondiv.group.addControl({
id: 'salutation',
controlType: ControlType.CONTROL_RADIOGROUP,
options: { valueOptions: [{ value: 'mr', label: 'Mr.' }, { value: 'ms', label: 'Ms.' }] },
validators: ['required'],
});
persondiv.group.addControl({
id: 'title',
controlType: ControlType.CONTROL_INPUT,
options: { label: 'Title', placeholder: 'Enter your title', maxLength: 30, minLength: 2 },
validators: ['minLength', 'maxLength'],
});
persondiv.group.addControl({
id: 'name',
controlType: ControlType.CONTROL_INPUT,
options: { label: 'name', placeholder: 'Enter your name', maxLength: 60, minLength: 4 },
validators: ['required', 'minLength', 'maxLength'],
});
// add controls to the button division:
buttondiv.group.addButton({
id: 'clear',
controlType: ControlType.CONTROL_BUTTON,
options: { label: 'Clear' },
action: 'clear',
});
buttondiv.group.addSeparator({
id: 'separatorMainButtons',
controlType: ControlType.CONTROL_SEPARATOR,
options: { css: { container: 'button-separator' } },
});
buttondiv.group.addButton({
id: 'reset',
controlType: ControlType.CONTROL_BUTTON,
options: { label: 'Reset' },
action: 'reset',
});
buttondiv.group.addButton({
id: 'submit',
controlType: ControlType.CONTROL_BUTTON,
options: { label: 'Submit' },
action: 'submit',
});
// create to form configuration:
const formConfig: FormConfig = form.toFormConfig();
- JSON configuration:
Configuration of the plunker example
- observable form value and form value update:
// subscribe to form value changes:
dynaForm.valueChanges.subscribe(...);
// get form value:
value = dynaForm.value;
// init form value (undefined for empty form):
dynaForm.initValue(value);
// clear form value:
dynaForm.clearValue();
// reset form value to the last value provided to 'initValue':
dynaForm.resetValue();
- observable control values and control value updates:
ctrlModel = dynaForm.findComponentById(id).model;
ctrlModel = dynaForm.model.findControlByPath(path);
// subscribe to form value changes:
ctrlModel.valueChanges.subscribe(...);
// get form value:
value = ctrlModel.value;
// patch/set form value:
ctrlModel.patchValue(value);
ctrlModel.setValue(value);
- observable focus changes and click events:
ctrlComp = dynaForm.findComponentById(id);
// subscribe to focus changes:
ctrlComp.focusChanges.subscribe((focus) => { console.log(focus ? 'got focus' : 'lost focus');});
// subscribe to click event:
ctrlComp.click.subscribe(...);
- get/set form data using an application data model to form data model mapping
In your form configuration define the JSON pointer to the application data model and use 'dynaForm.initValueFromAppModel' instead of 'dynaForm.initValue' and 'dynaForm.valueToAppModel' instead of 'dynaForm.value'
- enable/disable or show/hide fields based on conditions defined on related field values
In the relations expressions, you can combine all common arithmetic, comparison, and logical operators, as well as references to form control values. 'foo.bar' would reference the value of the 'bar' control in the 'foo' group; In this example, the 'newsletter' checkbox will only be enabled if the 'atc' checkbox has been selected:
group: [
{
key: 'atc',
controlType: ControlType.CONTROL_CHECKBOX,
...
},
{
key: 'newsletter',
controlType: ControlType.CONTROL_CHECKBOX,
...
relations: {enable: 'atc'}
}
]
- layout/styling
you can optionally assign additional CSS classes using ControlConfig.options.css and let angular-dynaform add them to the container, control, label sections of the corresponding component
The following CSS classes are predefined:
- on all components: 'adf-container', 'adf-control', 'adf-content', 'adf-error' and 'adf-label'
- on the form-control-component: 'adf-form-container', 'adf-form-control', 'adf-form-content', 'adf-form-error'
- on all group-components: 'adf-group-container', 'adf-group-control', 'adf-group-content', 'adf-group-error' and 'adf-group-label'
- on all array-components: 'adf-array-container', 'adf-array-control', 'adf-array-content', 'adf-array-error' and 'adf-array-label'
- on all array-header sections: 'adf-header-content'
- on all array-footer sections: 'adf-footer-content'
- on the array-item sections: 'adf-array-item' and if the item is selected: 'adf-array-item-selected'
- for all non-group and non-array-components: 'adf-control-container', 'adf-control-control', 'adf-control-content', 'adf-control-error' and 'adf-control-label'
you can subclass the base class DynamicFormControlComponentBase, the generic subclass DynamicFormControlComponent or any of the UI-specific subclasses to create your own custom control.
To be able to refer to this class from the form configuration, your class type must be registered with a new or existing name, which can then be used for the ControlConfig.controlType property. Please see DynamicFormService.setControlComponent for the registration.
Angulars dependency injection will be used to instantiate your component, so please do not forget to provide DynamicFormControlComponentBase using your existing class.
you can subclass DynamicFormAction to create your own dynamic form action.
To be able to refer to this action from the form configuration, your class type must be registered with a new or existing name, which can then be configured using the ControlConfig.action poperty. Please see DynamicFormService.actionTypes.setType for the registration.
implement either DynamicFormValidatorFn or DynamicFormAsyncValidatorFn and register your function using DynamicFormService.validatorFn.setFn or DynamicFormService.asyncValidatorFn.setFn
Model | Control | HTML | Material | Nativescript |
---|
| ArrayModel: | | Array | yes | yes | yes | | Group- or Subset-Model: | | Fieldset | yes | yes | yes | | | Division | yes | yes | yes | | | Tabgroup | | yes | | | | Stepper | | yes | | | ValueControlModel: | | Checkbox | yes | yes | yes | | | Datepicker | * | yes | yes** | | | Input | yes | yes | yes (TextField) | | | Radiogroup | yes | yes | yes | | | Select | yes | yes | | | | Slider | yes | yes | yes | | | Switch | yes | yes | yes | | | Textarea | yes | yes | yes (TextView) | | | Listpicker | | | yes | | NullControlModel: | | Button | yes | yes | yes | | | Separator | yes | yes | yes | | | | | | |
*) you can use "input type='date'" as replacement for a datepicker
**) reports date as utc-timestamp
e.g if timezone is "Central European Summer Time" and selected date is "2018-06-06" the value of the form control is set to "2018-06-05T22:00:00.000Z"
angular-dynaform is licensed under the MIT license