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.
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 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:
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>
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');
}
}
}
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.
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.
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.