Angular Dynamic Forms And Custom Validators Complete Guide

 Last Update:2025-06-22T00:00:00     .NET School AI Teacher - SELECT ANY TEXT TO EXPLANATION.    8 mins read      Difficulty-Level: beginner

Understanding the Core Concepts of Angular Dynamic Forms and Custom Validators

Angular Dynamic Forms and Custom Validators

Dynamic Forms in Angular

Dynamic forms in Angular refer to the creation of forms at runtime based on data models or external configurations. Unlike static forms which are hard-coded in the component template, dynamic forms allow developers to build forms dynamically by adding or removing form fields, changing form validation rules, and more, based on user interactions or predefined business logic.

Benefits of Dynamic Forms
  1. Flexibility: Ability to modify forms dynamically to adapt to various user scenarios or data-driven requirements.
  2. Reusability: Form components can be reused across different parts of the application.
  3. Maintainability: Easier to maintain and update forms as they are not hard-coded in templates.
Implementation Steps
  1. Define Configuration: Start by defining the form configuration which includes the form fields, their types, default values, and validation rules. This configuration can be static or fetched from an API.

    const formConfig = [
      { name: 'firstName', type: 'text', required: true, label: 'First Name' },
      { name: 'lastName', type: 'text', required: true, label: 'Last Name' },
      { name: 'email', type: 'email', required: true, label: 'Email' },
      { name: 'age', type: 'number', min: 18, label: 'Age' },
    ];
    
  2. Create Dynamic Form Group: Use Angular's FormBuilder to create a FormGroup based on the form configuration.

    import { FormBuilder, FormGroup, Validators } from '@angular/forms';
    
    constructor(private fb: FormBuilder) {}
    
    ngOnInit() {
      this.dynamicForm = this.fb.group({});
    
      formConfig.forEach((field) => {
        const control = this.fb.control(
          field.default ? field.default : '',
          field.required ? [Validators.required, ...this.getCustomValidators(field)] : [ ...this.getCustomValidators(field)]
        );
        this.dynamicForm.addControl(field.name, control);
      });
    }
    
  3. Render Form in Template: Create a loop in your template to iterate over the form controls and render the form fields.

    <form [formGroup]="dynamicForm">
      <div *ngFor="let field of formConfig">
        <label [for]="field.name">{{ field.label }}</label>
        <input *ngIf="field.type === 'text' || field.type === 'email'" [type]="field.type" [formControlName]="field.name" id="{{ field.name }}">
        <input *ngIf="field.type === 'number'" [type]="field.type" [formControlName]="field.name" id="{{ field.name }}" [min]="field.min">
        <small *ngIf="dynamicForm.get(field.name)?.invalid && (dynamicForm.get(field.name)?.dirty || dynamicForm.get(field.name)?.touched)">
          {{ getValidationMessage(field) }}
        </small>
      </div>
      <button [disabled]="dynamicForm.invalid">Submit</button>
    </form>
    

Custom Validators in Angular

Custom validators provide a means to add specific validation logic that is not covered by Angular’s built-in validators like required, minLength, maxLength, and email. This feature is invaluable when dealing with unique validation criteria specific to an application.

Benefits of Custom Validators
  1. Specificity: Custom validators allow developers to implement exact validation rules tailored to business needs.
  2. Reusability: Validator logic can be reused across multiple form controls.
  3. Simplicity: Encapsulating validation logic in custom validators helps in keeping form templates clean and focused.
Implementation Steps
  1. Create a Custom Validator Function:

    Custom validators are functions that take a FormControl as an argument and return an object with a validation key if the validation fails, or null if it passes.

    import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
    
    export function emailPatternValidator(): ValidatorFn {
      return (control: AbstractControl): ValidationErrors | null => {
        const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
        const value = control.value;
    
        if (value && !emailPattern.test(value)) {
          return { emailPattern: true };
        }
    
        return null;
      };
    }
    
  2. Apply the Custom Validator to a FormControl: Apply the custom validator to a form control using the Validators.compose method if combining with other validators.

    ngOnInit() {
      this.dynamicForm = this.fb.group({
        email: ['', Validators.compose([
          Validators.required,
          emailPatternValidator()
        ])]
      });
    }
    
  3. Display Validation Messages: Update the template to display validation messages based on the custom validation errors.

Online Code run

🔔 Note: Select your programming language to check or run code at

💻 Run Code Compiler

Step-by-Step Guide: How to Implement Angular Dynamic Forms and Custom Validators

Step 1: Set Up Your Angular Project

  1. Install Angular CLI (if you haven't already):

    npm install -g @angular/cli
    
  2. Create a new Angular project:

    ng new dynamic-forms-example
    
  3. Navigate to the project directory:

    cd dynamic-forms-example
    
  4. Serve the project to ensure everything is set up correctly:

    ng serve
    

    Navigate to http://localhost:4200/ in your web browser to see your application running.

Step 2: Create a Dynamic Form

  1. Generate a new component for our dynamic form:

    ng generate component dynamic-form
    
  2. Install ReactiveFormsModule: The ReactiveFormsModule in Angular contains the necessary services and parts to build forms in a reactive fashion. Open your app.module.ts file and import it:

    import { BrowserModule } from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { ReactiveFormsModule } from '@angular/forms';
    
    import { AppComponent } from './app.component';
    import { DynamicFormComponent } from './dynamic-form/dynamic-form.component';
    
    @NgModule({
      declarations: [
        AppComponent,
        DynamicFormComponent
      ],
      imports: [
        BrowserModule,
        ReactiveFormsModule
      ],
      providers: [],
      bootstrap: [AppComponent]
    })
    export class AppModule { }
    
  3. Set up the DynamicFormComponent: Open dynamic-form.component.ts and set up a form to dynamically add form controls.

    import { Component } from '@angular/core';
    import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';
    
    @Component({
      selector: 'app-dynamic-form',
      templateUrl: './dynamic-form.component.html',
      styleUrls: ['./dynamic-form.component.css']
    })
    export class DynamicFormComponent {
      form: FormGroup;
    
      constructor(private fb: FormBuilder) {
        this.form = this.fb.group({
          fields: this.fb.array([this.createField()])
        });
      }
    
      get fields() {
        return this.form.get('fields') as FormArray;
      }
    
      createField(): FormGroup {
        return this.fb.group({
          type: ['', Validators.required],
          value: ['']
        });
      }
    
      addField() {
        this.fields.push(this.createField());
      }
    
      removeField(index: number) {
        this.fields.removeAt(index);
      }
    
      onSubmit() {
        console.log('Form Submitted', this.form.value);
      }
    }
    
  4. Set up the template for the DynamicFormComponent: Open dynamic-form.component.html and add the necessary HTML to display and manipulate the form.

    <form [formGroup]="form" (ngSubmit)="onSubmit()">
      <div formArrayName="fields">
        <div *ngFor="let field of fields.controls; let i = index" [formGroupName]="i">
          <label>
            Field Type:
            <select formControlName="type">
              <option disabled>Select a type</option>
              <option>Text</option>
              <option>Email</option>
            </select>
          </label>
          <label>
            Value:
            <input formControlName="value" />
          </label>
          <button type="button" (click)="removeField(i)">Remove Field</button>
        </div>
      </div>
      <button type="button" (click)="addField()">Add Field</button>
      <button type="submit" [disabled]="form.invalid">Submit</button>
    </form>
    

Step 3: Create a Custom Validator

  1. Generate a new service for your custom validator:

    ng generate service custom-validator
    
  2. Implement the custom validator logic: Open custom-validator.service.ts and create a method for a custom validator.

    import { Injectable } from '@angular/core';
    import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
    
    @Injectable({
      providedIn: 'root'
    })
    export class CustomValidatorService {
    
      constructor() { }
    
      // Custom validator to validate email confirmation
      emailConfirmation(controlNameToMatch: string): ValidatorFn {
        return (control: AbstractControl): ValidationErrors | null => {
          if (control.parent && control.value !== control.parent.get(controlNameToMatch)?.value) {
            return { 'emailConfirmation': true };
          }
          return null;
        };
      }
    }
    
  3. Use the custom validator in your DynamicFormComponent: Inject the CustomValidatorService and use it in your form setup.

    import { Component } from '@angular/core';
    import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';
    import { CustomValidatorService } from '../custom-validator.service';
    
    @Component({
      selector: 'app-dynamic-form',
      templateUrl: './dynamic-form.component.html',
      styleUrls: ['./dynamic-form.component.css']
    })
    export class DynamicFormComponent {
      form: FormGroup;
    
      constructor(private fb: FormBuilder, private customValidator: CustomValidatorService) {
        this.form = this.fb.group({
          fields: this.fb.array([this.createField()]),
          email: ['', [Validators.required, Validators.email]],
          confirmEmail: ['', [Validators.required, this.customValidator.emailConfirmation('email')]]
        });
      }
    
      get fields() {
        return this.form.get('fields') as FormArray;
      }
    
      get email() {
        return this.form.get('email');
      }
    
      get confirmEmail() {
        return this.form.get('confirmEmail');
      }
    
      createField(): FormGroup {
        return this.fb.group({
          type: ['', Validators.required],
          value: ['']
        });
      }
    
      addField() {
        this.fields.push(this.createField());
      }
    
      removeField(index: number) {
        this.fields.removeAt(index);
      }
    
      onSubmit() {
        console.log('Form Submitted', this.form.value);
      }
    }
    
  4. Update the template to include the email and confirm email fields: Open dynamic-form.component.html and add the email and confirm email fields.

    <form [formGroup]="form" (ngSubmit)="onSubmit()">
      <div formArrayName="fields">
        <div *ngFor="let field of fields.controls; let i = index" [formGroupName]="i">
          <label>
            Field Type:
            <select formControlName="type">
              <option disabled>Select a type</option>
              <option>Text</option>
              <option>Email</option>
            </select>
          </label>
          <label>
            Value:
            <input formControlName="value" />
          </label>
          <button type="button" (click)="removeField(i)">Remove Field</button>
        </div>
      </div>
      <button type="button" (click)="addField()">Add Field</button>
      <label>
        Email:
        <input formControlName="email" />
        <div *ngIf="email?.touched && email?.invalid">
          <div *ngIf="email?.errors?.required">Email is required.</div>
          <div *ngIf="email?.errors?.email">Invalid email format.</div>
        </div>
      </label>
      <label>
        Confirm Email:
        <input formControlName="confirmEmail" />
        <div *ngIf="confirmEmail?.touched && confirmEmail?.invalid">
          <div *ngIf="confirmEmail?.errors?.required">Confirmation email is required.</div>
          <div *ngIf="confirmEmail?.errors?.emailConfirmation">Emails do not match.</div>
        </div>
      </label>
      <button type="submit" [disabled]="form.invalid">Submit</button>
    </form>
    

Step 4: Execute the Application

  1. Serve your application:

    ng serve
    
  2. Navigate to the application: Visit http://localhost:4200/ in your browser to see the dynamic form in action, and test the custom validators.

Top 10 Interview Questions & Answers on Angular Dynamic Forms and Custom Validators

Top 10 Questions and Answers on Angular Dynamic Forms and Custom Validators

1. What are Dynamic Forms in Angular?

2. How do you create a Dynamic Form in Angular using Reactive Forms?

To create a dynamic form using Reactive Forms, first make sure to import ReactiveFormsModule in your module. Next, you can define your form structure dynamically in the component class by creating FormGroup instances. Here's a simplified example:

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';

@Component({
  selector: 'app-dynamic-form',
  templateUrl: './dynamic-form.component.html',
  styleUrls: ['./dynamic-form.component.css']
})
export class DynamicFormComponent implements OnInit {
  form: FormGroup;
  formData = [
    { type: 'text', label: 'First Name', name: 'firstName' },
    { type: 'email', label: 'Email', name: 'email' },
  ];

  constructor(private fb: FormBuilder) {}

  ngOnInit() {
    this.form = this.fb.group({});
    this.addFormControl();
  }

  addFormControl() {
    this.formData.forEach(item => {
      this.form.addControl(item.name, this.fb.control(''));
    });
  }
}

In the template, you would loop through form.controls to render form controls dynamically.

3. What are Custom Validators in Angular?

Custom validators allow you to add your own validation logic to form controls that goes beyond Angular's built-in validators. They can be synchronous or asynchronous and help ensure that the form data meets specific business requirements.

4. How do you create a Custom Synchronous Validator in Angular?

To create a custom synchronous validator, you define a function that takes a FormControl as an argument and returns an object representing an error state if the validation fails. Here's how you can create a custom validator to check if the input contains only alphabetic characters:

import { AbstractControl, ValidationErrors } from '@angular/forms';

export function alphabeticValidator(control: AbstractControl): ValidationErrors | null {
  const value = control.value;
  const regex = /^[a-zA-Z]+$/;
  return regex.test(value) ? null : {'invalidAlphabetic': true};
}

Then apply it to a form control:

this.form.addControl('firstName', this.fb.control('', [alphabeticValidator]));

5. How do you use Custom Async Validators in Angular?

Asynchronous validators are used when validation needs to involve an external source, such as checking if a username is in use by querying a backend service. Here's how to create and use an async validator:

import { AbstractControl, AsyncValidator, ValidationErrors } from '@angular/forms';
import { Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { UserService } from '../user.service';

@Injectable({
  providedIn: 'root'
})
export class UniqueUsernameValidator implements AsyncValidator {
  constructor(private userService: UserService) {}

  validate(control: AbstractControl): Observable<ValidationErrors | null> {
    return this.userService.checkUsername(control.value).pipe(
      map(isAvailable => (isAvailable ? {'uniqueUsername': true} : null)),
      catchError(() => of(null))
    );
  }
}

Apply the async validator to a form control:

form: FormGroup;

constructor(private fb: FormBuilder, private uniqueUsernameValidator: UniqueUsernameValidator) {}

ngOnInit() {
  this.form = this.fb.group({
    username: ['', this.uniqueUsernameValidator.validate.bind(this.uniqueUsernameValidator)],
  });
}

6. How can you add multiple validators to a single form control in Angular?

You can add a combination of both synchronous and asynchronous validators to a form control by passing them as an array to the FormControl constructor:

this.form.addControl('email', this.fb.control('', [
  Validators.required,
  Validators.email,
  this.uniqueEmailValidator.validate.bind(this.uniqueEmailValidator)
]));

7. What is the best practice for creating dynamic controls and validators?

  • Modularize Code: Break down the code into smaller, reusable components.
  • Separate Data from UI: Store the form definition separately from the component’s logic.
  • Use Custom Validators: Create and use custom validators for complex validation rules.
  • Test Extensively: Thoroughly test your dynamic forms to ensure all possible scenarios are addressed.

8. How do you handle form submission of a Dynamic Form?

When handling form submission, ensure that form controls are properly validated and that the data is formatted correctly for the server or downstream processes. You can use the form.valid property to check if the form is valid before submission.

onSubmit() {
  if (this.form.valid) {
    const formData = this.form.value;
    // submit formData
  } else {
    console.log('Form is invalid');
  }
}

9. How can you dynamically update form controls and validators based on user input?

Use the valueChanges observable of a FormControl or FormGroup to listen for changes and adjust the form accordingly. For example, you might add or remove controls or validators based on the value of a field.

this.form.get('country').valueChanges.subscribe(country => {
  if (country === 'USA') {
    this.form.addControl('zipCode', this.fb.control('', [Validators.required, zipCodeValidator]));
  } else {
    this.form.removeControl('zipCode');
  }
});

10. How do you handle errors in a dynamic form?

Angular provides mechanisms to track errors on form controls. Use getError on controls to conditionally display error messages in the template. For example:

getErrorMessage(errorType: string) {
  const messages = {
    required: 'This field is required',
    email: 'Invalid email address',
    uniqueUsername: 'Username is already taken'
  };
  return messages[errorType];
}

In the template:

You May Like This Related .NET Topic

Login to post a comment.