From e3260f5d9747275b34b8a49691bd6b9ac3ce0f8e Mon Sep 17 00:00:00 2001 From: erdemcaygor Date: Mon, 29 Sep 2025 18:01:39 +0300 Subject: [PATCH 1/5] article added --- .../post.md | 281 ++++++++++++++++++ 1 file changed, 281 insertions(+) create mode 100644 docs/en/Community-Articles/2025-09-30-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications/post.md diff --git a/docs/en/Community-Articles/2025-09-30-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications/post.md b/docs/en/Community-Articles/2025-09-30-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications/post.md new file mode 100644 index 00000000000..40b311e19e5 --- /dev/null +++ b/docs/en/Community-Articles/2025-09-30-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications/post.md @@ -0,0 +1,281 @@ +# Building Dynamic Forms in Angular for Enterprise Applications + +## Introduction + +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. + +## Benefits + +1. **Flexibility**: Forms can be easily modified without changing the code. +2. **Reusability**: Form components can be shared across components. +3. **Maintainability**: Changes to form structures can be managed through configuration files or databases. +4. **Scalability**: New form fields and types can be added without significant code changes. +4. **User Experience**: Dynamic forms can adapt to user roles and permissions, providing a tailored experience. + +## Architecture + +### 1. Form Configuration Model + +We define a model to represent the form configuration. This model includes field types, labels, validation rules, and other metadata. + +```typescript +export interface FormFieldConfig { + key: string; + value?: any; + type: 'text' | 'email' | 'number' | 'select' | 'checkbox' | 'date' | 'textarea'; + label: string; + placeholder?: string; + required?: boolean; + disabled?: boolean; + options?: { key: string; value: any }[]; + validators?: ValidatorConfig[]; + conditionalLogic?: ConditionalRule[]; + order?: number; + gridSize?: number; // For layout purposes, e.g., Bootstrap grid size (1-12) +} + +export interface ValidatorConfig { + type: 'required' | 'email' | 'minLength' | 'maxLength' | 'pattern' | 'custom'; + value?: any; + message: string; +} + +// Conditional logic to show/hide or enable/disable fields based on other field values +export interface ConditionalRule { + dependsOn: string; + condition: 'equals' | 'notEquals' | 'contains' | 'greaterThan' | 'lessThan'; + value: any; + action: 'show' | 'hide' | 'enable' | 'disable'; +} + +``` +### 2. Dynamic Form Service + +A service to handle form creation and validation processes. + +```typescript +@Injectable({ + providedIn: 'root' +}) +export class DynamicFormService { + + createFormGroup(fields: FormFieldConfig[]): FormGroup { + const group: any = {}; + + fields.forEach(field => { + const validators = this.buildValidators(field.validators || []); + const initialValue = this.getInitialValue(field); + + group[field.key] = new FormControl({ + value: initialValue, + disabled: field.disabled || false + }, validators); + }); + + return new FormGroup(group); + } + + private buildValidators(validatorConfigs: ValidatorConfig[]): ValidatorFn[] { + return validatorConfigs.map(config => { + switch (config.type) { + case 'required': + return Validators.required; + case 'email': + return Validators.email; + case 'minLength': + return Validators.minLength(config.value); + case 'maxLength': + return Validators.maxLength(config.value); + case 'pattern': + return Validators.pattern(config.value); + default: + return Validators.nullValidator; + } + }); + } + + private getInitialValue(field: FormFieldConfig): any { + switch (field.type) { + case 'checkbox': + return false; + case 'number': + return 0; + default: + return ''; + } + } +} + +``` + +### 3. Dynamic Form Component + +```typescript +@Component({ + selector: 'app-dynamic-form', + template: ` +
+ @for (field of sortedFields; track field.key) { +
+
+ + +
+
+ } +
+ + +
+
+ `, + styles: [` + .dynamic-form { + display: flex; + gap: 0.5rem; + flex-direction: column; + } + .form-actions { + display: flex; + justify-content: flex-end; + gap: 0.5rem; + } + `], + imports: [ReactiveFormsModule, CommonModule, DynamicFormFieldComponent], +}) +export class DynamicFormComponent implements OnInit { + fields = input([]); + submitButtonText = input('Submit'); + formSubmit = output(); + formCancel = output(); + private dynamicFormService = inject(DynamicFormService); + + dynamicForm!: FormGroup; + isSubmitting = false; + fieldVisibility: { [key: string]: boolean } = {}; + + ngOnInit() { + this.dynamicForm = this.dynamicFormService.createFormGroup(this.fields()); + this.initializeFieldVisibility(); + this.setupConditionalLogic(); + } + + get sortedFields(): FormFieldConfig[] { + return this.fields().sort((a, b) => (a.order || 0) - (b.order || 0)); + } + + onSubmit() { + if (this.dynamicForm.valid) { + this.isSubmitting = true; + this.formSubmit.emit(this.dynamicForm.value); + } else { + this.markAllFieldsAsTouched(); + } + } + + onCancel() { + this.formCancel.emit(); + } + + onFieldChange(event: { fieldKey: string; value: any }) { + this.evaluateConditionalLogic(event.fieldKey); + } + + isFieldVisible(field: FormFieldConfig): boolean { + return this.fieldVisibility[field.key] !== false; + } + + private initializeFieldVisibility() { + this.fields().forEach(field => { + this.fieldVisibility[field.key] = !field.conditionalLogic?.length; + }); + } + + private setupConditionalLogic() { + this.fields().forEach(field => { + if (field.conditionalLogic) { + field.conditionalLogic.forEach(rule => { + const dependentControl = this.dynamicForm.get(rule.dependsOn); + if (dependentControl) { + dependentControl.valueChanges.subscribe(() => { + this.evaluateConditionalLogic(field.key); + }); + } + }); + } + }); + } + + private evaluateConditionalLogic(fieldKey: string) { + const field = this.fields().find(f => f.key === fieldKey); + if (!field?.conditionalLogic) return; + + field.conditionalLogic.forEach(rule => { + const dependentValue = this.dynamicForm.get(rule.dependsOn)?.value; + const conditionMet = this.evaluateCondition(dependentValue, rule.condition, rule.value); + + this.applyConditionalAction(fieldKey, rule.action, conditionMet); + }); + } + + private evaluateCondition(fieldValue: any, condition: string, ruleValue: any): boolean { + switch (condition) { + case 'equals': + return fieldValue === ruleValue; + case 'notEquals': + return fieldValue !== ruleValue; + case 'contains': + return fieldValue && fieldValue.includes && fieldValue.includes(ruleValue); + case 'greaterThan': + return Number(fieldValue) > Number(ruleValue); + case 'lessThan': + return Number(fieldValue) < Number(ruleValue); + default: + return false; + } + } + + private applyConditionalAction(fieldKey: string, action: string, shouldApply: boolean) { + const control = this.dynamicForm.get(fieldKey); + + switch (action) { + case 'show': + this.fieldVisibility[fieldKey] = shouldApply; + break; + case 'hide': + this.fieldVisibility[fieldKey] = !shouldApply; + break; + case 'enable': + if (control) { + shouldApply ? control.enable() : control.disable(); + } + break; + case 'disable': + if (control) { + shouldApply ? control.disable() : control.enable(); + } + break; + } + } + + private markAllFieldsAsTouched() { + Object.keys(this.dynamicForm.controls).forEach(key => { + this.dynamicForm.get(key)?.markAsTouched(); + }); + } +} +``` \ No newline at end of file From 4357ce7b3abbacc7d2674cdc5f99132e3d3c1d3e Mon Sep 17 00:00:00 2001 From: erdemcaygor Date: Thu, 2 Oct 2025 12:10:42 +0300 Subject: [PATCH 2/5] refactoring --- .../post.md | 275 +++++++++++++++++- 1 file changed, 269 insertions(+), 6 deletions(-) diff --git a/docs/en/Community-Articles/2025-09-30-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications/post.md b/docs/en/Community-Articles/2025-09-30-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications/post.md index 40b311e19e5..7157c2111b5 100644 --- a/docs/en/Community-Articles/2025-09-30-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications/post.md +++ b/docs/en/Community-Articles/2025-09-30-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications/post.md @@ -16,7 +16,7 @@ Dynamic forms are useful for enterprise applications where form structures need ### 1. Form Configuration Model -We define a model to represent the form configuration. This model includes field types, labels, validation rules, and other metadata. +Define a model to represent the form configuration. This model includes field types, labels, validation rules, and other metadata. ```typescript export interface FormFieldConfig { @@ -27,13 +27,14 @@ export interface FormFieldConfig { placeholder?: string; required?: boolean; disabled?: boolean; - options?: { key: string; value: any }[]; - validators?: ValidatorConfig[]; - conditionalLogic?: ConditionalRule[]; - order?: number; + options?: { key: string; value: any }[]; + validators?: ValidatorConfig[]; // Custom validators + conditionalLogic?: ConditionalRule[]; // For showing/hiding fields based on other field values + order?: number; // For ordering fields in the form gridSize?: number; // For layout purposes, e.g., Bootstrap grid size (1-12) } +// Validator configuration for form fields export interface ValidatorConfig { type: 'required' | 'email' | 'minLength' | 'maxLength' | 'pattern' | 'custom'; value?: any; @@ -278,4 +279,266 @@ export class DynamicFormComponent implements OnInit { }); } } -``` \ No newline at end of file +``` + +### 4. Dynamic Form Field Component + +```typescript +@Component({ + selector: 'app-dynamic-form-field', + template: ` + @if (isVisible) { +
+ + @if (field.type === 'text') { + +
+ + + @if (isFieldInvalid()) { +
+ {{ getErrorMessage() }} +
+ } +
+ } @else if (field.type === 'select') { + +
+ + + @if (isFieldInvalid()) { +
+ {{ getErrorMessage() }} +
+ } +
+ } @else if (field.type === 'checkbox') { + +
+ + + @if (isFieldInvalid()) { +
+ {{ getErrorMessage() }} +
+ } +
+ } @else if (field.type === 'email') { + +
+ + + @if (isFieldInvalid()) { +
+ {{ getErrorMessage() }} +
+ } +
+ } @else if (field.type === 'textarea') { + +
+ + + @if (isFieldInvalid()) { +
+ {{ getErrorMessage() }} +
+ } +
+ } +
+ + } + `, + imports: [ReactiveFormsModule], +}) +export class DynamicFormFieldComponent implements OnInit { + @Input() field!: FormFieldConfig; + @Input() form!: FormGroup; + @Input() isVisible: boolean = true; + @Output() fieldChange = new EventEmitter<{ fieldKey: string; value: any }>(); + + ngOnInit() { + const control = this.form.get(this.field.key); + if (control) { + control.valueChanges.subscribe(value => { + this.fieldChange.emit({ fieldKey: this.field.key, value }); + }); + } + } + + isFieldInvalid(): boolean { + const control = this.form.get(this.field.key); + return !!(control && control.invalid && (control.dirty || control.touched)); + } + + getErrorMessage(): string { + const control = this.form.get(this.field.key); + if (!control || !control.errors) return ''; + + const validators = this.field.validators || []; + + for (const validator of validators) { + if (control.errors[validator.type]) { + return validator.message; + } + } + + // Fallback error messages + if (control.errors['required']) return `${this.field.label} is required`; + if (control.errors['email']) return 'Please enter a valid email address'; + if (control.errors['minlength']) return `Minimum length is ${control.errors['minlength'].requiredLength}`; + if (control.errors['maxlength']) return `Maximum length is ${control.errors['maxlength'].requiredLength}`; + + return 'Invalid input'; + } +} + +``` + +### 5. Usage Example + +```typescript + +@Component({ + selector: 'app-home', + template: ` +
+
+ + +
+
+ `, + imports: [DynamicFormComponent] +}) +export class HomeComponent { + @Input() title: string = 'Home Component'; + formFields: FormFieldConfig[] = [ + { + key: 'firstName', + type: 'text', + label: 'First Name', + placeholder: 'Enter first name', + required: true, + validators: [ + { type: 'required', message: 'First name is required' }, + { type: 'minLength', value: 2, message: 'Minimum 2 characters required' } + ], + gridSize: 12, + order: 1 + }, + { + key: 'lastName', + type: 'text', + label: 'Last Name', + placeholder: 'Enter last name', + required: true, + validators: [ + { type: 'required', message: 'Last name is required' } + ], + gridSize: 12, + order: 2 + }, + { + key: 'email', + type: 'email', + label: 'Email Address', + placeholder: 'Enter email', + required: true, + validators: [ + { type: 'required', message: 'Email is required' }, + { type: 'email', message: 'Please enter a valid email' } + ], + order: 3 + }, + { + key: 'userType', + type: 'select', + label: 'User Type', + required: true, + options: [ + { key: 'admin', value: 'Administrator' }, + { key: 'user', value: 'Regular User' }, + { key: 'guest', value: 'Guest User' } + ], + validators: [ + { type: 'required', message: 'Please select user type' } + ], + order: 4 + }, + { + key: 'adminNotes', + type: 'textarea', + label: 'Admin Notes', + placeholder: 'Enter admin-specific notes', + conditionalLogic: [ + { + dependsOn: 'userType', + condition: 'equals', + value: 'admin', + action: 'show' + } + ], + order: 5 + } + ]; + + onSubmit(formData: any) { + console.log('Form submitted:', formData); + // Handle form submission + } + + onCancel() { + console.log('Form cancelled'); + // Handle form cancellation + } +} + + +``` + +## Conclusion + +Dynamic forms provide a powerful foundation for enterprise applications that need flexible, maintainable form solutions. By separating form configuration from implementation, teams can create scalable systems that adapt to changing business requirements without extensive code modifications. The key to success is building a robust architecture that handles validation, conditional logic, and user experience considerations while maintaining performance and accessibility standards. \ No newline at end of file From df9f7ab83d91b96ddfd50535afcec2bb7e6664e9 Mon Sep 17 00:00:00 2001 From: erdemcaygor Date: Fri, 3 Oct 2025 01:39:06 +0300 Subject: [PATCH 3/5] article update --- .../form.png | Bin 0 -> 39559 bytes .../post.md | 31 ++++++++++++++---- 2 files changed, 24 insertions(+), 7 deletions(-) create mode 100644 docs/en/Community-Articles/2025-09-30-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications/form.png diff --git a/docs/en/Community-Articles/2025-09-30-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications/form.png b/docs/en/Community-Articles/2025-09-30-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications/form.png new file mode 100644 index 0000000000000000000000000000000000000000..e84188c60d5603c37dfab112cac39cd159df52e5 GIT binary patch literal 39559 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

1seB8AAHL`5I+eRl37AkYOp73_33TZxph0 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 literal 0 HcmV?d00001 diff --git a/docs/en/Community-Articles/2025-09-30-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications/post.md b/docs/en/Community-Articles/2025-09-30-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications/post.md index 7157c2111b5..5674ab422a9 100644 --- a/docs/en/Community-Articles/2025-09-30-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications/post.md +++ b/docs/en/Community-Articles/2025-09-30-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications/post.md @@ -14,10 +14,12 @@ Dynamic forms are useful for enterprise applications where form structures need ## Architecture -### 1. Form Configuration Model +### 1. Defining Form Configuration Models -Define a model to represent the form configuration. This model includes field types, labels, validation rules, and other metadata. +We will define form configuration model as a first step. This models stores field types, labels, validation rules, and other metadata. +#### 1.1. Form Field Configuration +Form field configuration interface represents individual form fields and contains properties like type, label, validation rules and conditional logic. ```typescript export interface FormFieldConfig { key: string; @@ -33,26 +35,33 @@ export interface FormFieldConfig { order?: number; // For ordering fields in the form gridSize?: number; // For layout purposes, e.g., Bootstrap grid size (1-12) } +``` +#### 1.2. Validator Configuration -// Validator configuration for form fields +Validator configuration interface defines validation rules for form fields. +```typescript export interface ValidatorConfig { type: 'required' | 'email' | 'minLength' | 'maxLength' | 'pattern' | 'custom'; value?: any; message: string; } +``` -// Conditional logic to show/hide or enable/disable fields based on other field values +#### 1.3. Conditional Logic + +Conditional logic interface defines rules for showing/hiding or enabling/disabling fields based on other field values. +```typescript export interface ConditionalRule { dependsOn: string; condition: 'equals' | 'notEquals' | 'contains' | 'greaterThan' | 'lessThan'; value: any; action: 'show' | 'hide' | 'enable' | 'disable'; } - ``` + ### 2. Dynamic Form Service -A service to handle form creation and validation processes. +We will create dynamic form service to handle form creation and validation processes. ```typescript @Injectable({ @@ -60,6 +69,7 @@ A service to handle form creation and validation processes. }) export class DynamicFormService { + // Create form group based on fields createFormGroup(fields: FormFieldConfig[]): FormGroup { const group: any = {}; @@ -76,6 +86,7 @@ export class DynamicFormService { return new FormGroup(group); } + // Returns an array of form field validators based on the validator configurations private buildValidators(validatorConfigs: ValidatorConfig[]): ValidatorFn[] { return validatorConfigs.map(config => { switch (config.type) { @@ -111,6 +122,7 @@ export class DynamicFormService { ### 3. Dynamic Form Component +The main component that renders the form based on the configuration it receives as input. ```typescript @Component({ selector: 'app-dynamic-form', @@ -283,6 +295,7 @@ export class DynamicFormComponent implements OnInit { ### 4. Dynamic Form Field Component +This component renders individual form fields, handling different types and validation messages based on the configuration. ```typescript @Component({ selector: 'app-dynamic-form-field', @@ -539,6 +552,10 @@ export class HomeComponent { ``` +## Result + +![example_form](./form.png) + ## Conclusion -Dynamic forms provide a powerful foundation for enterprise applications that need flexible, maintainable form solutions. By separating form configuration from implementation, teams can create scalable systems that adapt to changing business requirements without extensive code modifications. The key to success is building a robust architecture that handles validation, conditional logic, and user experience considerations while maintaining performance and accessibility standards. \ No newline at end of file +These kinds of components are essential for large applications because they allow for rapid development and easy maintenance. By defining forms through configuration, developers can quickly adapt to changing requirements without extensive code changes. This approach also promotes consistency across the application, as the same form components can be reused in different contexts. \ No newline at end of file From d87ad3294ae2a91b292553d14d6d0ff6c183ed5e Mon Sep 17 00:00:00 2001 From: erdemcaygor Date: Fri, 3 Oct 2025 16:26:21 +0300 Subject: [PATCH 4/5] article update --- .../form.png | Bin .../post.md | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename docs/en/Community-Articles/{2025-09-30-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications => 2025-10-03-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications}/form.png (100%) rename docs/en/Community-Articles/{2025-09-30-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications => 2025-10-03-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications}/post.md (100%) diff --git a/docs/en/Community-Articles/2025-09-30-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications/form.png b/docs/en/Community-Articles/2025-10-03-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications/form.png similarity index 100% rename from docs/en/Community-Articles/2025-09-30-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications/form.png rename to docs/en/Community-Articles/2025-10-03-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications/form.png diff --git a/docs/en/Community-Articles/2025-09-30-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications/post.md b/docs/en/Community-Articles/2025-10-03-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications/post.md similarity index 100% rename from docs/en/Community-Articles/2025-09-30-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications/post.md rename to docs/en/Community-Articles/2025-10-03-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications/post.md From f40579b40c32e613c5d943b10e2ce30bb42b3824 Mon Sep 17 00:00:00 2001 From: erdemcaygor Date: Mon, 6 Oct 2025 09:28:56 +0300 Subject: [PATCH 5/5] article update --- .../form.png | Bin .../post.md | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename docs/en/Community-Articles/{2025-10-03-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications => 2025-10-06-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications}/form.png (100%) rename docs/en/Community-Articles/{2025-10-03-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications => 2025-10-06-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications}/post.md (100%) diff --git a/docs/en/Community-Articles/2025-10-03-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications/form.png b/docs/en/Community-Articles/2025-10-06-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications/form.png similarity index 100% rename from docs/en/Community-Articles/2025-10-03-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications/form.png rename to docs/en/Community-Articles/2025-10-06-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications/form.png diff --git a/docs/en/Community-Articles/2025-10-03-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications/post.md b/docs/en/Community-Articles/2025-10-06-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications/post.md similarity index 100% rename from docs/en/Community-Articles/2025-10-03-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications/post.md rename to docs/en/Community-Articles/2025-10-06-Building-Dynamic-Forms-in-Angular-for-Enterprise-Applications/post.md