Telerik blogs

See how to use Angular v18’s new unified event handling system to manage form events with a single subscription, improving code readability and maintainability.

Angular is a widely used JavaScript framework known for its ability to create dynamic and interactive web applications. One of Angular’s strengths is its powerful form handling, and with the release of Angular v18, unified control state events now exist, which simplify the process of building and managing forms. In this article, we’ll explore this new events property and how it can help streamline form-building in Angular applications.

Forms in Angular

Angular provides two main approaches to managing user input and building forms: template-driven forms and reactive forms. Template-driven forms enable the use of form directives directly within the Angular template, while reactive forms take a model-based approach for creating forms.

Reactive Forms: A Model-Driven Approach

Reactive forms offer a powerful and explicit way to manage forms in Angular and are suitable for complex scenarios where we need fine-grained control over form validation, dynamic updates and data flow. Reactive forms provide us with:

  • An explicit form model – In reactive forms, we define the form’s structure and validation rules programmatically in our component class. This explicit model makes the form’s logic transparent and easier to reason about.
  • An immutable data flow – Reactive forms utilize an immutable approach to data management. Each change to the form’s state creates a new immutable state object, ensuring data integrity and simplifying change tracking.
  • Synchronous data access – Reactive forms provide synchronous access to the form’s data model, making it straightforward to retrieve and update values.

Let’s illustrate this behavior with a simple reactive form example.

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

@Component({
  selector: 'app-profile-form',
  templateUrl: './profile-form.component.html',
})
export class ProfileFormComponent {
  profileForm = new FormGroup({
    firstName: new FormControl('', Validators.required),
    lastName: new FormControl(''),
    email: new FormControl('', [Validators.required, Validators.email]),
  });

  onSubmit() {
    console.log(this.profileForm.value);
  }
}

In the above example, we define a form model using FormGroup and FormControl. This creates a form that contains three fields: firstName, lastName and email. The firstName and email fields are set to be required using the Validators.required validation rule. Additionally, the email field uses Validators.email to check that the input is a valid email address.

To make the form functional in the template, we need to register the controls using Angular’s form directives such as formGroup and formControlName. Here’s an example of how we can bind the form controls to the template.

<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
  <label for="firstName">First Name</label>
  <input id="firstName" formControlName="firstName">

  <label for="lastName">Last Name</label>
  <input id="lastName" formControlName="lastName">

  <label for="email">Email</label>
  <input id="email" formControlName="email">

  <button type="submit" [disabled]="profileForm.invalid">Submit</button>
</form>

Listening for Value Changes

We can typically subscribe to various observables on form controls, such as valueChanges, statusChanges and others, to react to specific control events. Let’s build on top of the existing ProfileFormComponent we have above to incorporate Angular’s valueChanges observable, allowing us to listen for real-time changes in form control values. We can use this feature to add custom behavior, such as dynamically validating fields or performing side effects based on user input.

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

@Component({
  selector: 'app-profile-form',
  templateUrl: './profile-form.component.html',
})
export class ProfileFormComponent implements OnInit {
  profileForm = new FormGroup({
    firstName: new FormControl('', Validators.required),
    lastName: new FormControl(''),
    email: new FormControl('', [Validators.required, Validators.email]),
  });

  ngOnInit() {
    // Subscribing to value changes on firstName
    this.profileForm.get('firstName')?.valueChanges.subscribe(value => {
      console.log('First Name changed to:', value);
      // We can add custom behavior here, such as validation or formatting
    });

    // Subscribing to value changes on email
    this.profileForm.get('email')?.valueChanges.subscribe(value => {
      console.log('Email changed to:', value);
      // Trigger custom behavior based on the email input
    });
  }

  onSubmit() {
    if (this.profileForm.valid) {
      console.log('Form Submitted:', this.profileForm.value);
    } else {
      console.log('Form is invalid');
    }
  }
}

Unified Control State Change Events in Angular v18

In earlier versions of Angular, handling form events involved subscribing to multiple observables, such as valueChanges and statusChanges like we’ve seen above. This often led to multiple subscriptions, which can clutter the code if not properly managed. Angular v18 introduces a unified event handler to solve this issue: the events property on form controls like FormControl, FormGroup and FormArray. This new feature provides a streamlined way to manage all control events with a single subscription.

Let’s build on our earlier ProfileFormComponent to demonstrate how the new event system works. In this updated version, we’ll subscribe to the events property to handle different types of control state changes in one place.

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

@Component({
  selector: 'app-profile-form',
  templateUrl: './profile-form.component.html',
})
export class ProfileFormComponent implements OnInit {
  profileForm = new FormGroup({
    firstName: new FormControl('', Validators.required),
    lastName: new FormControl(''),
    email: new FormControl('', [Validators.required, Validators.email]),
  });

  ngOnInit() {
    // Subscribing to the unified events observable on the entire form group    
    this.profileForm.events.subscribe(event => {
      // process the individual events
    });
  }

  onSubmit() {
    if (this.profileForm.valid) {
      console.log('Form Submitted:', this.profileForm.value);
    } else {
      console.log('Form is invalid');
    }
  }
}

For example, we can filter out specific events such as ValueChangeEvent to handle only value changes in the form. This allows us to further customize the behavior based on the specific event type.

ngOnInit() {
  this.profileForm.events
    .pipe(filter((event) => event instanceof ValueChangeEvent))
    .subscribe((event) => {
      console.log('Value:', event);
    });
}

This approach simplifies form event handling, so that the code is more maintainable and efficient, especially when dealing with complex forms that have many controls.

Wrap-up

With Angular v18’s new unified event handling system, managing form events has become more straightforward. Instead of subscribing to multiple observables for each form control, we can now handle all events using a single subscription, which improves code readability and maintainability. This feature, combined with Angular’s powerful reactive forms, makes it easier to build dynamic and interactive forms in our applications. If you’re looking to upgrade to Angular v18, this is a great feature to explore (among others!), as it can streamline how we manage forms and handle user interactions.


About the Author

Hassan Djirdeh

Hassan is a senior frontend engineer and has helped build large production applications at-scale at organizations like Doordash, Instacart and Shopify. Hassan is also a published author and course instructor where he’s helped thousands of students learn in-depth frontend engineering skills like React, Vue, TypeScript, and GraphQL.

Related Posts

Comments

Comments are disabled in preview mode.