Skip to content

Commit e3260f5

Browse files
committed
article added
1 parent c25ad9e commit e3260f5

File tree

1 file changed

+281
-0
lines changed
  • docs/en/Community-Articles/2025-09-30-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications

1 file changed

+281
-0
lines changed
Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
# Building Dynamic Forms in Angular for Enterprise Applications
2+
3+
## Introduction
4+
5+
Dynamic forms are useful for enterprise applications where form structures need to be flexible, configurable, and generated at runtime based on business requirements. This approach allows developers to create forms from configuration objects rather than hardcoding them, enabling greater flexibility and maintainability.
6+
7+
## Benefits
8+
9+
1. **Flexibility**: Forms can be easily modified without changing the code.
10+
2. **Reusability**: Form components can be shared across components.
11+
3. **Maintainability**: Changes to form structures can be managed through configuration files or databases.
12+
4. **Scalability**: New form fields and types can be added without significant code changes.
13+
4. **User Experience**: Dynamic forms can adapt to user roles and permissions, providing a tailored experience.
14+
15+
## Architecture
16+
17+
### 1. Form Configuration Model
18+
19+
We define a model to represent the form configuration. This model includes field types, labels, validation rules, and other metadata.
20+
21+
```typescript
22+
export interface FormFieldConfig {
23+
key: string;
24+
value?: any;
25+
type: 'text' | 'email' | 'number' | 'select' | 'checkbox' | 'date' | 'textarea';
26+
label: string;
27+
placeholder?: string;
28+
required?: boolean;
29+
disabled?: boolean;
30+
options?: { key: string; value: any }[];
31+
validators?: ValidatorConfig[];
32+
conditionalLogic?: ConditionalRule[];
33+
order?: number;
34+
gridSize?: number; // For layout purposes, e.g., Bootstrap grid size (1-12)
35+
}
36+
37+
export interface ValidatorConfig {
38+
type: 'required' | 'email' | 'minLength' | 'maxLength' | 'pattern' | 'custom';
39+
value?: any;
40+
message: string;
41+
}
42+
43+
// Conditional logic to show/hide or enable/disable fields based on other field values
44+
export interface ConditionalRule {
45+
dependsOn: string;
46+
condition: 'equals' | 'notEquals' | 'contains' | 'greaterThan' | 'lessThan';
47+
value: any;
48+
action: 'show' | 'hide' | 'enable' | 'disable';
49+
}
50+
51+
```
52+
### 2. Dynamic Form Service
53+
54+
A service to handle form creation and validation processes.
55+
56+
```typescript
57+
@Injectable({
58+
providedIn: 'root'
59+
})
60+
export class DynamicFormService {
61+
62+
createFormGroup(fields: FormFieldConfig[]): FormGroup {
63+
const group: any = {};
64+
65+
fields.forEach(field => {
66+
const validators = this.buildValidators(field.validators || []);
67+
const initialValue = this.getInitialValue(field);
68+
69+
group[field.key] = new FormControl({
70+
value: initialValue,
71+
disabled: field.disabled || false
72+
}, validators);
73+
});
74+
75+
return new FormGroup(group);
76+
}
77+
78+
private buildValidators(validatorConfigs: ValidatorConfig[]): ValidatorFn[] {
79+
return validatorConfigs.map(config => {
80+
switch (config.type) {
81+
case 'required':
82+
return Validators.required;
83+
case 'email':
84+
return Validators.email;
85+
case 'minLength':
86+
return Validators.minLength(config.value);
87+
case 'maxLength':
88+
return Validators.maxLength(config.value);
89+
case 'pattern':
90+
return Validators.pattern(config.value);
91+
default:
92+
return Validators.nullValidator;
93+
}
94+
});
95+
}
96+
97+
private getInitialValue(field: FormFieldConfig): any {
98+
switch (field.type) {
99+
case 'checkbox':
100+
return false;
101+
case 'number':
102+
return 0;
103+
default:
104+
return '';
105+
}
106+
}
107+
}
108+
109+
```
110+
111+
### 3. Dynamic Form Component
112+
113+
```typescript
114+
@Component({
115+
selector: 'app-dynamic-form',
116+
template: `
117+
<form [formGroup]="dynamicForm" (ngSubmit)="onSubmit()" class="dynamic-form">
118+
@for (field of sortedFields; track field.key) {
119+
<div class="row">
120+
<div [ngClass]="'col-md-' + (field.gridSize || 12)">
121+
<app-dynamic-form-field
122+
[field]="field"
123+
[form]="dynamicForm"
124+
[isVisible]="isFieldVisible(field)"
125+
(fieldChange)="onFieldChange($event)">
126+
</app-dynamic-form-field>
127+
</div>
128+
</div>
129+
}
130+
<div class="form-actions">
131+
<button
132+
type="button"
133+
class="btn btn-secondary"
134+
(click)="onCancel()">
135+
Cancel
136+
</button>
137+
<button
138+
type="submit"
139+
class="btn btn-primary"
140+
[disabled]="!dynamicForm.valid || isSubmitting">
141+
{{ submitButtonText() }}
142+
</button>
143+
</div>
144+
</form>
145+
`,
146+
styles: [`
147+
.dynamic-form {
148+
display: flex;
149+
gap: 0.5rem;
150+
flex-direction: column;
151+
}
152+
.form-actions {
153+
display: flex;
154+
justify-content: flex-end;
155+
gap: 0.5rem;
156+
}
157+
`],
158+
imports: [ReactiveFormsModule, CommonModule, DynamicFormFieldComponent],
159+
})
160+
export class DynamicFormComponent implements OnInit {
161+
fields = input<FormFieldConfig[]>([]);
162+
submitButtonText = input<string>('Submit');
163+
formSubmit = output<any>();
164+
formCancel = output<void>();
165+
private dynamicFormService = inject(DynamicFormService);
166+
167+
dynamicForm!: FormGroup;
168+
isSubmitting = false;
169+
fieldVisibility: { [key: string]: boolean } = {};
170+
171+
ngOnInit() {
172+
this.dynamicForm = this.dynamicFormService.createFormGroup(this.fields());
173+
this.initializeFieldVisibility();
174+
this.setupConditionalLogic();
175+
}
176+
177+
get sortedFields(): FormFieldConfig[] {
178+
return this.fields().sort((a, b) => (a.order || 0) - (b.order || 0));
179+
}
180+
181+
onSubmit() {
182+
if (this.dynamicForm.valid) {
183+
this.isSubmitting = true;
184+
this.formSubmit.emit(this.dynamicForm.value);
185+
} else {
186+
this.markAllFieldsAsTouched();
187+
}
188+
}
189+
190+
onCancel() {
191+
this.formCancel.emit();
192+
}
193+
194+
onFieldChange(event: { fieldKey: string; value: any }) {
195+
this.evaluateConditionalLogic(event.fieldKey);
196+
}
197+
198+
isFieldVisible(field: FormFieldConfig): boolean {
199+
return this.fieldVisibility[field.key] !== false;
200+
}
201+
202+
private initializeFieldVisibility() {
203+
this.fields().forEach(field => {
204+
this.fieldVisibility[field.key] = !field.conditionalLogic?.length;
205+
});
206+
}
207+
208+
private setupConditionalLogic() {
209+
this.fields().forEach(field => {
210+
if (field.conditionalLogic) {
211+
field.conditionalLogic.forEach(rule => {
212+
const dependentControl = this.dynamicForm.get(rule.dependsOn);
213+
if (dependentControl) {
214+
dependentControl.valueChanges.subscribe(() => {
215+
this.evaluateConditionalLogic(field.key);
216+
});
217+
}
218+
});
219+
}
220+
});
221+
}
222+
223+
private evaluateConditionalLogic(fieldKey: string) {
224+
const field = this.fields().find(f => f.key === fieldKey);
225+
if (!field?.conditionalLogic) return;
226+
227+
field.conditionalLogic.forEach(rule => {
228+
const dependentValue = this.dynamicForm.get(rule.dependsOn)?.value;
229+
const conditionMet = this.evaluateCondition(dependentValue, rule.condition, rule.value);
230+
231+
this.applyConditionalAction(fieldKey, rule.action, conditionMet);
232+
});
233+
}
234+
235+
private evaluateCondition(fieldValue: any, condition: string, ruleValue: any): boolean {
236+
switch (condition) {
237+
case 'equals':
238+
return fieldValue === ruleValue;
239+
case 'notEquals':
240+
return fieldValue !== ruleValue;
241+
case 'contains':
242+
return fieldValue && fieldValue.includes && fieldValue.includes(ruleValue);
243+
case 'greaterThan':
244+
return Number(fieldValue) > Number(ruleValue);
245+
case 'lessThan':
246+
return Number(fieldValue) < Number(ruleValue);
247+
default:
248+
return false;
249+
}
250+
}
251+
252+
private applyConditionalAction(fieldKey: string, action: string, shouldApply: boolean) {
253+
const control = this.dynamicForm.get(fieldKey);
254+
255+
switch (action) {
256+
case 'show':
257+
this.fieldVisibility[fieldKey] = shouldApply;
258+
break;
259+
case 'hide':
260+
this.fieldVisibility[fieldKey] = !shouldApply;
261+
break;
262+
case 'enable':
263+
if (control) {
264+
shouldApply ? control.enable() : control.disable();
265+
}
266+
break;
267+
case 'disable':
268+
if (control) {
269+
shouldApply ? control.disable() : control.enable();
270+
}
271+
break;
272+
}
273+
}
274+
275+
private markAllFieldsAsTouched() {
276+
Object.keys(this.dynamicForm.controls).forEach(key => {
277+
this.dynamicForm.get(key)?.markAsTouched();
278+
});
279+
}
280+
}
281+
```

0 commit comments

Comments
 (0)