Dynamic Form
This library generates Angular FormGroup
and related DOM from a specified JSON configuration. Such configuration can
either be baked in into the application or received dynamically from back-end. Exact form input fields are NOT part of
the library and must be provided by a library user. A set of Material based input components can be found in
Material Components For Ngx Dynamic Form
.
Ngx Dynamic Form
is only compatible with Angular v11+.
Installation
Install the library through NPM:
$ npm i @elemental-concept/ngx-dynamic-form
NgxDynamicFormModule
should be included into your Angular modules automatically by IDE. If your IDE doesn't support such
functionality, don't forget to add it manually:
import { NgxDynamicFormModule } from '@elemental-concept/ngx-dynamic-form';
@NgModule({
imports: [
NgxDynamicFormModule,
]
})
class SomeModule {
}
Usage
The library provides DynamicFormComponent
and DynamicFormFactoryService
to generate forms in run-time. In most
cases DynamicFormComponent
should be used directly. DynamicFormFactoryService
can be used to create a completely
custom solution when supplied component is not enough.
DynamicFormComponent
expects form configuration, form value and optional input component mapping. Form configuration
describes all fields, their labels, validations, etc in a simple format. Value is a regular key-value map as used
by FormGroup
. DynamicFormComponent
also provides a set of events for application to react to data and state changes.
Additionally, custom HTML can be placed inside the form if needed.
Simple Example
<!-- my-form.component.html -->
<ecdf-dynamic-form [config]="config" [value]="value"></ecdf-dynamic-form>
import { Component } from '@angular/core';
@Component({
selector: 'my-form',
templateUrl: './my-form.component.html',
styleUrls: [ './my-form.component.scss' ]
})
export class MyFormComponent {
config = {
elements: [
{
id: 'firstName',
label: 'First name',
type: 'string',
validators: [ { type: 'required' } ]
},
{
id: 'lastName',
label: 'Last name',
type: 'string',
validators: [ { type: 'required' } ]
}
]
};
value = {
firstName: 'John',
lastName: 'Doe'
};
}
Adding Custom HTML To The Form
DynamicFormComponent
has 4 content slots which can be used to annotate and decorate the form according to the design.
One of these slots should also contain submit button if required. Slots are specified by CSS classes:
before-form
- content will be placed befor <form>
tag.form-header
- content will be placed at the start of <form>
tag.form-footer
- content will be placed at the end of <form>
tag.after-form
- content will be placed after <form>
tag.
<!-- my-form.component.html -->
<ecdf-dynamic-form [config]="config" [value]="value">
<h3 class="before-form">Sign-up form</h3>
<p class="form-header">All fields are mandatory</p>
<div class="form-footer">
<button>Sign up now!</button>
</div>
<p class="after-form">Terms and conditions.</p>
</ecdf-dynamic-form>
This will render the following structure:
<h3 class="before-form">Sign-up form</h3>
<form>
<p class="form-header">All fields are mandatory</p>
<div class="form-footer">
<button>Sign up now!</button>
</div>
</form>
<p class="after-form">Terms and conditions.</p>
API
DynamicFormComponent
@Input config: DynamicFormConfig<M>
Required. Specifies a configuration of input fields and related data. Please check DynamicFormConfig
section for more
info.
@Input value: { [ key: string ]: any }
Required. Value of the form. Can be an empty object {}
.
@Input componentMap: DynamicFormComponentMap<M>
Mapping between textual input types like string
and checkbox
to actual Angular components. This mapping should be
provided either by component library like Material Components For Dynamic Form or by the app developer manually.
Check DynamicFormComponentMap
section for more info.
@Output valueChanges: EventEmitter<DynamicFormComponentValue>
Fires an event each time there are any changes to a form value.
@Output statusChanges: EventEmitter<DynamicFormComponentStatus>
Fires an event each time there are any status changes to a form.
@Output formSubmit: EventEmitter<DynamicFormComponentValue>
Fires an event when the user submits the form.
DynamicFormConfig
This is where all the magic happens. This configuration allows developers to describe forms of varying complexities to
suit most needs. Each form element describes basic attributes like id and label as well as validations, relationships
between different form elements, error messages, etc. M
describes meta information format used by the chosen set of
components. Meta information allows for additional input component customisation, like CSS classes, etc.
interface DynamicFormConfig<M> {
elements: DynamicFormElement<M>[];
endpoint?: string;
}
interface DynamicFormElement<M> {
id: string;
label: string;
type: '_description_' | string;
placeholder?: string;
meta?: M;
options?: DynamicFormElementOption[];
dependsOn?: DynamicFormElementRelationship[];
validators?: DynamicFormValidator[];
errors?: DynamicFormErrors;
}
Required attributes:
id
- unique ID of a form field, for example, password
or email
.label
- label to show next to the form field. Exact location and presentation of the label will depend on form
component library.type
- type of form field. Types depend on a form component library. Dynamic Form
does not care about types at all
as it does not render the final HTML into the view.
Optional attributes:
placeholder
- can be used by a component library to display a placeholder when form field value is empty.meta
- a set of meta attributes which are passed directly to a component library without any modifications. Meta
handling is completely dependent on a component library.options
- list of possible values for a form field. Behaviour depends on a component library.dependsOn
- list of dependencies which control when form field will be visible. For example, a text input field
might only appear when a check box is selected. dependsOn
allows you to express such behaviour.validators
- a set of Angular form validators to be applied to a form field.errors
- a set of error messages to show when validations fail.
Example:
{
elements: [
{
id: 'firstName',
label: 'First name',
type: 'string',
validators: [ { type: 'required' } ]
},
{
id: 'lastName',
label: 'Last name',
type: 'string',
validators: [ { type: 'required' } ]
}
]
}
DynamicFormElementOption
interface DynamicFormElementOption {
label: string;
value: any;
}
label
- label of an option visible to the end user.value
- value of an option which will be assigned to the form field when a user selects it.
DynamicFormElementRelationship
Dynamic Form
allows specifying basic relationships between form fields. All relationships in a set are merged
with AND
logical operation. Five operations are available:
equals
lessThan
moreThan
set
notSet
Example
{
elements: [
{
id: 'subscribe',
label: 'Do you wish to subscribe to our newsletter?',
type: 'checkbox'
},
{
id: 'email',
label: 'Your e-mail for newsletter subscription',
type: 'email',
validators: [ { type: 'email' } ],
dependsOn: [ { id: 'subscribe', type: 'set' } ]
}
]
}
In this example email
field will only be presented to the user when check box subscribe
is checked.
Complete interface reference:
interface DynamicFormElementRelationshipCommon {
id: string;
}
interface DynamicFormElementRelationshipEquals extends DynamicFormElementRelationshipCommon {
type: 'equals';
value: any;
}
interface DynamicFormElementRelationshipLessThan extends DynamicFormElementRelationshipCommon {
type: 'lessThan';
value: number;
}
interface DynamicFormElementRelationshipMoreThan extends DynamicFormElementRelationshipCommon {
type: 'moreThan';
value: number;
}
interface DynamicFormElementRelationshipIsSet extends DynamicFormElementRelationshipCommon {
type: 'set';
}
interface DynamicFormElementRelationshipIsNotSet extends DynamicFormElementRelationshipCommon {
type: 'notSet';
}
type DynamicFormElementRelationship =
DynamicFormElementRelationshipEquals |
DynamicFormElementRelationshipLessThan |
DynamicFormElementRelationshipMoreThan |
DynamicFormElementRelationshipIsSet |
DynamicFormElementRelationshipIsNotSet;
DynamicFormValidator
Describes an Angular form validator to be assigned to a form field. It's just one-to-one mapping to existing validators.
Custom validators are not supported yet.
Example:
{
elements: [
{
id: 'email',
label: 'E-mail',
type: 'email',
validators: [ { type: 'required' }, { type: 'email' } ]
}
]
}
This is equal to:
new FormGroup({
email: new FormControl('', [ Validators.required, Validators.email ])
});
Complete interface reference:
interface DynamicFormValidatorMin {
type: 'min';
value: number;
}
interface DynamicFormValidatorMax {
type: 'max';
value: number;
}
interface DynamicFormValidatorRequired {
type: 'required';
}
interface DynamicFormValidatorRequiredTrue {
type: 'requiredTrue';
}
interface DynamicFormValidatorEmail {
type: 'email';
}
interface DynamicFormValidatorMinLength {
type: 'minLength';
length: number;
}
interface DynamicFormValidatorMaxLength {
type: 'maxLength';
length: number;
}
interface DynamicFormValidatorPattern {
type: 'pattern';
pattern: string;
}
export interface DynamicFormValidatorEqualTo {
type: 'equalTo';
field: string;
}
type DynamicFormValidator =
DynamicFormValidatorMin |
DynamicFormValidatorMax |
DynamicFormValidatorRequired |
DynamicFormValidatorRequiredTrue |
DynamicFormValidatorEmail |
DynamicFormValidatorPattern |
DynamicFormValidatorEqualTo;
DynamicFormErrors
interface DynamicFormErrors {
[ key: string ]: string;
}
A simple key-value map between error codes and their messages.
Creating Custom Input Components
This section is not finished yet. Please check the source code in the sample app on how it's done as well as
Material Components For Dynamic Form mentioned earlier.