Reactive Forms In Angular with Practical Example

Introduction

Angular is a popular framework for building dynamic web applications, and one of its key features is its powerful forms module. Angular offers two types of forms: Template-driven forms and Reactive forms. In this article, we'll delve into reactive forms in Angular, exploring their benefits, how to create them, and how to work with them effectively.

What are reactive forms? Reactive forms, also known as model-driven forms, are a more flexible and scalable approach to building forms in Angular. Unlike template-driven forms, where the form's behavior is largely defined in the template, reactive forms are defined programmatically using TypeScript classes to create form control objects.

Key Benefits of Reactive Forms

  1. More control and flexibility: Reactive forms give developers more control over form validation, error handling, and dynamic form controls.
  2. Immutable data model: Reactive forms use an immutable data model, making it easier to track changes and manage form states.
  3. Better support for complex forms: Reactive forms are well-suited for building complex forms with dynamic form controls and complex validation requirements.
  4. Easier unit testing: Since form controls are created programmatically as TypeScript objects, they are easier to unit test compared to template-driven forms.

Creating Reactive Forms

To create a reactive form in Angular, follow these steps:

Import the necessary Angular modules

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

Define the form in the component class

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

@Component({
  selector: 'app-contact-form',
  templateUrl: './contact-form.component.html',
  styleUrl: './contact-form.component.css'
})
export class ContactFormComponent {

  contactForm!: FormGroup;

  constructor(private fb: FormBuilder) {
    this.formBuilder();
  }

  private formBuilder() {
    debugger;
    this.contactForm = this.fb.group({
      name: ['',Validators.required],
      email: ['',[Validators.email,Validators.required]],
      password: ['', [
        Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*])[a-zA-Z\d!@#$%^&*]{6,}$/), // Updated pattern
        Validators.minLength(6),
        Validators.required
      ]],
      message: ['',Validators.required]
    });
  }

  onSubmit() {
    debugger;
    if (this.contactForm.valid) {
      console.log(this.contactForm.value); // You can send this data to your backend or perform other actions
      this.contactForm.reset(); // Optional: Reset the form after submission
    } else {
      // Mark all form fields as touched to display validation errors
      this.markFormGroupTouched(this.contactForm);
    }
  }

  markFormGroupTouched(formGroup: FormGroup) {
    debugger;
    Object.values(formGroup.controls).forEach(control => {
      control.markAsTouched();
      if (control instanceof FormGroup) {
        this.markFormGroupTouched(control);
      }
    });
  }
}

Bind the form to the template

<div class="container">
  <div class="card mt-4">
    <div class="card-body">
      <form [formGroup]="contactForm" (ngSubmit)="onSubmit()">
        <div class="mb-3">
          <label for="name" class="form-label">Name:</label>
          <input type="text" id="name" class="form-control" formControlName="name">
          <div *ngIf="contactForm.get('name')?.errors && contactForm.get('name')?.touched" class="invalid-feedback">
            <div *ngIf="contactForm.get('name')?.hasError('required')">Name is required.</div>
          </div>
        </div>
        <div class="mb-3">
          <label for="email" class="form-label">Email:</label>
          <input type="email" id="email" class="form-control" formControlName="email">
          <div *ngIf="contactForm.get('email')?.errors && contactForm.get('email')?.touched" class="invalid-feedback">
            <div *ngIf="contactForm.get('email')?.hasError('required')">Email is required.</div>
            <div *ngIf="contactForm.get('email')?.hasError('email')">Invalid email format.</div>
          </div>
        </div>
        <div class="mb-3">
          <label for="password" class="form-label">Password:</label>
          <input type="password" id="password" class="form-control" formControlName="password">
          <div *ngIf="contactForm.get('password')?.errors && contactForm.get('password')?.touched" class="invalid-feedback">
            <div *ngIf="contactForm.get('password')?.hasError('required')">Password is required.</div>
            <div *ngIf="contactForm.get('password')?.hasError('minlength')">Password must be at least 6 characters long.</div>
            <div *ngIf="contactForm.get('password')?.hasError('pattern')">Password must contain at least one uppercase letter, one lowercase letter, and one digit and special character.</div>
          </div>
        </div>
        <div class="mb-3">
          <label for="message" class="form-label">Message:</label>
          <textarea id="message" class="form-control" formControlName="message"></textarea>
          <div *ngIf="contactForm.get('message')?.errors && contactForm.get('message')?.touched" class="invalid-feedback">
            <div *ngIf="contactForm.get('message')?.hasError('required')">Message is required.</div>
          </div>
        </div>
        <button type="submit" class="btn btn-primary" [disabled]="contactForm.invalid">Submit</button>
      </form>
    </div>
  </div>
</div>

Implement form submission logic in the component class

onSubmit() {
    debugger;
    if (this.contactForm.valid) {
      console.log(this.contactForm.value); // You can send this data to your backend or perform other actions
      this.contactForm.reset(); // Optional: Reset the form after submission
    } else {
      // Mark all form fields as touched to display validation errors
      this.markFormGroupTouched(this.contactForm);
    }
  }

Working with Reactive Forms

Once the form is created, you can interact with it programmatically to access form controls, validate form inputs, and react to changes. Here are some common tasks when working with reactive forms:

const name = this.contactForm.get('name');

Add the Validators

private formBuilder() {
    debugger;
    this.contactForm = this.fb.group({
      name: ['',Validators.required],
      email: ['',[Validators.email,Validators.required]],
      password: ['', [
        Validators.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*])[a-zA-Z\d!@#$%^&*]{6,}$/), // Updated pattern
        Validators.minLength(6),
        Validators.required
      ]],
      message: ['',Validators.required]
    });
  }

Reacting to Form Changes

You can subscribe to value changes or status changes of the form or individual form controls to react to changes in the form:

this.contactForm.valueChanges.subscribe(value => {
  console.log('Form value changed:', value);
});

Output

Angular Reactive form

Angular Reactive form

GitHub Project Url: https://github.com/SardarMudassarAliKhan/ReactiveFormsInAngualr

Conclusion

Reactive forms offer a powerful and flexible way to build forms in Angular applications. By using TypeScript classes to define form controls programmatically, developers gain more control over form behavior, validation, and error handling. With the ability to handle complex forms and ease of unit testing, reactive forms are a preferred choice for many Angular developers when building dynamic and scalable web forms.