19 KiB
//[doc-seo]
{
"Description": "Learn how to use the ABP Dynamic Form Module to create dynamic, configurable forms with validation, conditional logic, and custom components in Angular applications."
}
Dynamic Form Module
The ABP Dynamic Form Module is a powerful component that allows you to create dynamic, configurable forms without writing extensive HTML templates. It provides a declarative way to define form fields with validation, conditional logic, grid layout, and custom components.
Installation
The Dynamic Form Module is part of the @abp/ng.components package. If you haven't installed it yet, install it via npm:
npm install @abp/ng.components
Usage
Import the DynamicFormComponent in your component:
import { DynamicFormComponent } from '@abp/ng.components/dynamic-form';
@Component({
selector: 'app-my-component',
imports: [DynamicFormComponent],
templateUrl: './my-component.component.html',
})
export class MyComponent {}
Basic Example
Here's a simple example of how to use the dynamic form:
import { Component } from '@angular/core';
import { DynamicFormComponent } from '@abp/ng.components/dynamic-form';
import { FormFieldConfig } from '@abp/ng.components/dynamic-form';
@Component({
selector: 'app-user-form',
imports: [DynamicFormComponent],
template: `
<abp-dynamic-form
[fields]="formFields"
[submitButtonText]="'Submit'"
[showCancelButton]="true"
(onSubmit)="handleSubmit($event)"
(formCancel)="handleCancel()">
</abp-dynamic-form>
`,
})
export class UserFormComponent {
formFields: FormFieldConfig[] = [
{
key: 'firstName',
type: 'text',
label: 'First Name',
placeholder: 'Enter your first name',
required: true,
order: 1,
},
{
key: 'lastName',
type: 'text',
label: 'Last Name',
placeholder: 'Enter your last name',
required: true,
order: 2,
},
{
key: 'email',
type: 'email',
label: 'Email',
placeholder: 'Enter your email',
required: true,
order: 3,
},
];
handleSubmit(formValue: any) {
console.log('Form submitted:', formValue);
// Handle form submission
}
handleCancel() {
console.log('Form cancelled');
// Handle form cancellation
}
}
Component Inputs
The DynamicFormComponent accepts the following inputs:
| Input | Type | Default | Description |
|---|---|---|---|
fields |
FormFieldConfig[] |
[] |
Array of field configurations |
values |
Record<string, any> |
undefined |
Initial values for the form |
submitButtonText |
string |
'Submit' |
Text for the submit button |
submitInProgress |
boolean |
false |
Whether form submission is in progress |
showCancelButton |
boolean |
false |
Whether to show the cancel button |
Component Outputs
| Output | Type | Description |
|---|---|---|
onSubmit |
EventEmitter<any> |
Emitted when the form is submitted with valid data |
formCancel |
EventEmitter<void> |
Emitted when the cancel button is clicked |
FormFieldConfig Properties
The FormFieldConfig interface defines the structure of each field in the form:
interface FormFieldConfig {
key: string; // Unique identifier for the field
type: FieldType; // Type of the field
label: string; // Label text for the field
value?: any; // Initial value
placeholder?: string; // Placeholder text
required?: boolean; // Whether the field is required
disabled?: boolean; // Whether the field is disabled
options?: OptionProps; // Options for select fields
validators?: ValidatorConfig[]; // Array of validator configurations
conditionalLogic?: ConditionalRule[]; // Array of conditional rules
order?: number; // Display order (ascending)
gridSize?: number; // Bootstrap grid size (1-12)
component?: Type<ControlValueAccessor>; // Custom component
}
Field Types
The following field types are supported:
text- Text inputemail- Email inputnumber- Number inputselect- Dropdown selectcheckbox- Checkboxdate- Date pickertextarea- Textarea
Validators
You can add validators to your form fields using the validators property:
const formFields: FormFieldConfig[] = [
{
key: 'username',
type: 'text',
label: 'Username',
validators: [
{
type: 'required',
message: 'Username is required',
},
{
type: 'minLength',
value: 3,
message: 'Username must be at least 3 characters',
},
{
type: 'maxLength',
value: 20,
message: 'Username must not exceed 20 characters',
},
],
},
{
key: 'age',
type: 'number',
label: 'Age',
validators: [
{
type: 'min',
value: 18,
message: 'You must be at least 18 years old',
},
{
type: 'max',
value: 100,
message: 'Age must not exceed 100',
},
],
},
];
Available Validator Types
| Type | Description | Requires Value |
|---|---|---|
required |
Field is required | No |
email |
Must be a valid email | No |
minLength |
Minimum string length | Yes |
maxLength |
Maximum string length | Yes |
min |
Minimum numeric value | Yes |
max |
Maximum numeric value | Yes |
pattern |
Regular expression pattern | Yes |
requiredTrue |
Must be true (for checkboxes) | No |
Select Fields with Options
You can create dropdown select fields with static or dynamic options:
Static Options
const formFields: FormFieldConfig[] = [
{
key: 'country',
type: 'select',
label: 'Country',
options: {
defaultValues: [
{ key: 'us', value: 'United States' },
{ key: 'uk', value: 'United Kingdom' },
{ key: 'ca', value: 'Canada' },
],
valueProp: 'key',
labelProp: 'value',
},
},
];
Dynamic Options from API
const formFields: FormFieldConfig[] = [
{
key: 'department',
type: 'select',
label: 'Department',
options: {
url: '/api/departments',
apiName: 'MyApi',
valueProp: 'id',
labelProp: 'name',
},
},
];
OptionProps Interface
interface OptionProps<T = any> {
defaultValues?: T[]; // Static array of options
url?: string; // API endpoint URL
disabled?: (option: T) => boolean; // Function to disable specific options
labelProp?: string; // Property name for label
valueProp?: string; // Property name for value
apiName?: string; // API name for RestService
}
Conditional Logic
The Dynamic Form Module supports conditional logic to show/hide or enable/disable fields based on other field values:
const formFields: FormFieldConfig[] = [
{
key: 'hasLicense',
type: 'checkbox',
label: 'Do you have a driver\'s license?',
order: 1,
},
{
key: 'licenseNumber',
type: 'text',
label: 'License Number',
placeholder: 'Enter your license number',
order: 2,
conditionalLogic: [
{
dependsOn: 'hasLicense',
condition: 'equals',
value: true,
action: 'show',
},
],
},
{
key: 'age',
type: 'number',
label: 'Age',
order: 3,
},
{
key: 'parentConsent',
type: 'checkbox',
label: 'Parent Consent Required',
order: 4,
conditionalLogic: [
{
dependsOn: 'age',
condition: 'lessThan',
value: 18,
action: 'show',
},
],
},
];
Conditional Rule Interface
interface ConditionalRule {
dependsOn: string; // Key of the field to watch
condition: string; // Condition type
value: any; // Value to compare against
action: string; // Action to perform
}
Available Conditions
equals- Field value equals the specified valuenotEquals- Field value does not equal the specified valuecontains- Field value contains the specified value (for strings/arrays)greaterThan- Field value is greater than the specified value (for numbers)lessThan- Field value is less than the specified value (for numbers)
Available Actions
show- Show the field when condition is methide- Hide the field when condition is metenable- Enable the field when condition is metdisable- Disable the field when condition is met
Grid Layout
You can use the gridSize property to control the Bootstrap grid layout:
const formFields: FormFieldConfig[] = [
{
key: 'firstName',
type: 'text',
label: 'First Name',
gridSize: 6, // Half width
order: 1,
},
{
key: 'lastName',
type: 'text',
label: 'Last Name',
gridSize: 6, // Half width
order: 2,
},
{
key: 'address',
type: 'textarea',
label: 'Address',
gridSize: 12, // Full width
order: 3,
},
];
The gridSize property uses Bootstrap's 12-column grid system. If not specified, it defaults to 12 (full width).
Custom Components
You can use custom components for specific fields by providing a component that implements ControlValueAccessor:
// custom-rating.component.ts
import { Component, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
@Component({
selector: 'app-custom-rating',
template: `
<div class="rating">
@for (star of [1,2,3,4,5]; track star) {
<span
class="star"
[class.filled]="star <= value"
(click)="setValue(star)">
★
</span>
}
</div>
`,
styles: [`
.star { cursor: pointer; font-size: 24px; color: #ccc; }
.star.filled { color: #ffc107; }
`],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomRatingComponent),
multi: true
}]
})
export class CustomRatingComponent implements ControlValueAccessor {
value = 0;
onChange: any = () => {};
onTouched: any = () => {};
setValue(rating: number) {
this.value = rating;
this.onChange(rating);
this.onTouched();
}
writeValue(value: any): void {
this.value = value || 0;
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
}
Then use it in your form configuration:
import { CustomRatingComponent } from './custom-rating.component';
const formFields: FormFieldConfig[] = [
{
key: 'rating',
type: 'text', // Type is ignored when using custom component
label: 'Rating',
component: CustomRatingComponent,
value: 3,
},
];
Setting Initial Values
You can set initial values for the form fields in two ways:
1. Using the value property in FormFieldConfig
const formFields: FormFieldConfig[] = [
{
key: 'firstName',
type: 'text',
label: 'First Name',
value: 'John',
},
];
2. Using the values input
@Component({
template: `
<abp-dynamic-form
[fields]="formFields"
[values]="initialValues"
(onSubmit)="handleSubmit($event)">
</abp-dynamic-form>
`,
})
export class MyComponent {
formFields: FormFieldConfig[] = [
{
key: 'firstName',
type: 'text',
label: 'First Name',
},
{
key: 'lastName',
type: 'text',
label: 'Last Name',
},
];
initialValues = {
firstName: 'John',
lastName: 'Doe',
};
handleSubmit(formValue: any) {
console.log(formValue);
}
}
Programmatic Form Control
You can access the form instance using the exportAs property and template reference variable:
@Component({
template: `
<abp-dynamic-form
#myForm="abpDynamicForm"
[fields]="formFields"
(onSubmit)="handleSubmit($event)">
</abp-dynamic-form>
<button (click)="myForm.resetForm()">Reset Form</button>
`,
})
export class MyComponent {
formFields: FormFieldConfig[] = [
// ... field configurations
];
handleSubmit(formValue: any) {
console.log(formValue);
}
}
Available Methods
resetForm()- Resets the form to its initial statesubmit()- Programmatically submit the form
Custom Action Buttons
You can customize the action buttons by projecting your own content:
@Component({
template: `
<abp-dynamic-form
[fields]="formFields"
(onSubmit)="handleSubmit($event)">
<div actions class="form-actions">
<button type="button" class="btn btn-secondary" (click)="handleCancel()">
Cancel
</button>
<button type="submit" class="btn btn-success">
Save Changes
</button>
<button type="button" class="btn btn-info" (click)="handleDraft()">
Save as Draft
</button>
</div>
</abp-dynamic-form>
`,
})
export class MyComponent {
formFields: FormFieldConfig[] = [
// ... field configurations
];
handleSubmit(formValue: any) {
console.log('Form submitted:', formValue);
}
handleCancel() {
console.log('Cancelled');
}
handleDraft() {
console.log('Saved as draft');
}
}
Complete Example
Here's a complete example demonstrating various features:
import { Component } from '@angular/core';
import { DynamicFormComponent } from '@abp/ng.components/dynamic-form';
import { FormFieldConfig } from '@abp/ng.components/dynamic-form';
@Component({
selector: 'app-employee-form',
imports: [DynamicFormComponent],
template: `
<div class="container">
<h2>Employee Registration</h2>
<abp-dynamic-form
#employeeForm="abpDynamicForm"
[fields]="formFields"
[submitButtonText]="'Register Employee'"
[showCancelButton]="true"
[submitInProgress]="isSubmitting"
(onSubmit)="handleSubmit($event)"
(formCancel)="handleCancel()">
</abp-dynamic-form>
</div>
`,
})
export class EmployeeFormComponent {
isSubmitting = false;
formFields: FormFieldConfig[] = [
// Personal Information
{
key: 'firstName',
type: 'text',
label: 'First Name',
placeholder: 'Enter first name',
required: true,
gridSize: 6,
order: 1,
validators: [
{ type: 'required', message: 'First name is required' },
{ type: 'minLength', value: 2, message: 'First name must be at least 2 characters' },
],
},
{
key: 'lastName',
type: 'text',
label: 'Last Name',
placeholder: 'Enter last name',
required: true,
gridSize: 6,
order: 2,
validators: [
{ type: 'required', message: 'Last name is required' },
{ type: 'minLength', value: 2, message: 'Last name must be at least 2 characters' },
],
},
{
key: 'email',
type: 'email',
label: 'Email',
placeholder: 'Enter email address',
required: true,
gridSize: 6,
order: 3,
validators: [
{ type: 'required', message: 'Email is required' },
{ type: 'email', message: 'Please enter a valid email address' },
],
},
{
key: 'phoneNumber',
type: 'text',
label: 'Phone Number',
placeholder: 'Enter phone number',
gridSize: 6,
order: 4,
},
// Employment Details
{
key: 'department',
type: 'select',
label: 'Department',
required: true,
gridSize: 6,
order: 5,
options: {
defaultValues: [
{ id: 1, name: 'Engineering' },
{ id: 2, name: 'Marketing' },
{ id: 3, name: 'Sales' },
{ id: 4, name: 'Human Resources' },
],
valueProp: 'id',
labelProp: 'name',
},
validators: [
{ type: 'required', message: 'Department is required' },
],
},
{
key: 'position',
type: 'text',
label: 'Position',
placeholder: 'Enter position',
required: true,
gridSize: 6,
order: 6,
validators: [
{ type: 'required', message: 'Position is required' },
],
},
{
key: 'startDate',
type: 'date',
label: 'Start Date',
required: true,
gridSize: 6,
order: 7,
validators: [
{ type: 'required', message: 'Start date is required' },
],
},
// Conditional Fields
{
key: 'isManager',
type: 'checkbox',
label: 'Is this person a manager?',
gridSize: 12,
order: 8,
},
{
key: 'teamSize',
type: 'number',
label: 'Team Size',
placeholder: 'Number of team members',
gridSize: 6,
order: 9,
conditionalLogic: [
{
dependsOn: 'isManager',
condition: 'equals',
value: true,
action: 'show',
},
],
validators: [
{ type: 'min', value: 1, message: 'Team size must be at least 1' },
],
},
{
key: 'managementExperience',
type: 'textarea',
label: 'Management Experience',
placeholder: 'Describe your management experience',
gridSize: 12,
order: 10,
conditionalLogic: [
{
dependsOn: 'isManager',
condition: 'equals',
value: true,
action: 'show',
},
],
},
// Additional Information
{
key: 'notes',
type: 'textarea',
label: 'Additional Notes',
placeholder: 'Any additional information',
gridSize: 12,
order: 11,
},
];
handleSubmit(formValue: any) {
this.isSubmitting = true;
console.log('Employee Data:', formValue);
// Simulate API call
setTimeout(() => {
this.isSubmitting = false;
alert('Employee registered successfully!');
}, 2000);
}
handleCancel() {
if (confirm('Are you sure you want to cancel?')) {
// Navigate back or reset form
console.log('Form cancelled');
}
}
}
API Reference
DynamicFormComponent
Properties
| Property | Type | Description |
|---|---|---|
dynamicForm |
FormGroup |
The underlying Angular FormGroup instance |
fieldVisibility |
{ [key: string]: boolean } |
Object tracking field visibility state |
Methods
| Method | Parameters | Returns | Description |
|---|---|---|---|
submit() |
- | void |
Submits the form if valid |
onCancel() |
- | void |
Emits the formCancel event |
resetForm() |
- | void |
Resets the form to initial values |
isFieldVisible(field) |
FormFieldConfig |
boolean |
Checks if a field is currently visible |
DynamicFormService
The DynamicFormService provides utility methods for form management:
Methods
| Method | Parameters | Returns | Description |
|---|---|---|---|
createFormGroup(fields) |
FormFieldConfig[] |
FormGroup |
Creates a FormGroup from field configurations |
getInitialValues(fields) |
FormFieldConfig[] |
any |
Extracts initial values from field configurations |
getOptions(url, apiName?) |
string, string? |
Observable<any[]> |
Fetches options from an API endpoint |