1. Blog
  2. Software Development
  3. How to Get Started with Angular Reactive Forms
Software Development

How to Get Started with Angular Reactive Forms

Learn how to create and implement Angular reactive forms in your web applications. This step-by-step guide covers the basics for beginners.

Justice Erolin

By Justice Erolin

As BairesDev CTO, Justice Erolin translates BairesDev's vision into technical roadmaps through the planning and coordinating of engineering teams.

20 min read

Featured image

Forms are a core part of every application, from login pages to sign-up forms, and Angular’s reactive forms make managing them easier and more efficient. Unlike template-driven forms, reactive forms offer better control and scalability, allowing development teams to handle complex use cases without sacrificing performance.

Angular is a widely-used JavaScript web application framework that helps build scalable, dynamic apps—making it a favorite among enterprise development teams. Reactive forms fit into Angular’s structure by offering robust form controls that handle user inputs, validation, and continuous changes. With form control instances, it’s easy to set the initial value and track every update. This approach simplifies validation and keeps forms in sync with data.

By using reactive forms, developers can build complex forms faster, ensuring performance, accessibility, and user experience stay intact. This method is built for the kind of large-scale applications mid-to-large enterprises need to deliver results.

What are Angular Reactive Forms?

When using Angular, developers can choose between two approaches for working with forms: reactive forms or template-driven forms.

Template-driven forms rely on a simpler structure where the logic and validations are defined in the template rather than the component class. This approach allows developers to build an internal form model directly within the HTML, but it can be limiting for more complex use cases.

Reactive forms, on the other hand, take a more model-oriented approach. The form control instance and logic are defined in the component class, giving developers more control over the form model. This method allows for better handling of value and validity status and enables precise synchronization with the underlying data model. The form itself appears in the HTML with special markup, keeping the logic separate and easier to maintain.

Each approach has its benefits and drawbacks. Reactive forms provide more flexibility and control, especially for handling complex forms or heavy user interactions. While they take more time to set up and have a steeper learning curve, they excel at managing dynamic forms, advanced validation rules, and scalability.

In contrast, template-driven forms spread their logic between the component and template, making them harder to manage in larger applications. Their asynchronous nature can introduce slight delays between user input and the form model update. While easier to learn, this approach struggles with large forms or complex validations, which can limit performance in more demanding applications.

Setting Up Angular for Reactive Forms

Creating Reactive forms requires developers to know how to install Angular and successfully set up their projects.

Installing Angular

Developers have to meet a few prerequisites to begin. They also need some basic knowledge of Angular to try this approach.

First, developers should install Node.js for Angular CLI to run. Angular CLI is a command-line interface tool designed to facilitate the initialization, development, scaffolding, maintenance, and testing of Angular applications.

To install it on Windows, developers need to open their Windows Command Prompt and enter the command:

npm install -g @angular/cli

When installed on a Mac, it may sometimes resist Angular CLI installation. If that happens, developers should use a sudo command and try installing again.

sudo npm install -g @angular/cli

Setting Up the Project

To start, developers should setup a new Angular project:

  1. Create a New Angular Project:ng new my-angular-app
  2. Navigate to the Project and Start the App:cd

Configuring a project to use Reactive forms requires developers to import the ReactiveFormsModule, which contains of the necessary building blocks. It allows access to the reactive forms API and facilitates the dynamic generation, modification, and validation of form components.

1.  Open the App Module: Navigate to the app.module.ts file, which is typically in the src/app/ directory.

2. Import ReactiveFormsModule: Add the following import statement at the top of the file:

import { ReactiveFormsModule } from '@angular/forms';

3. Add ReactiveFormsModule to the Imports Array: In the @NgModule decorator, inside the imports array, add ReactiveFormsModule to ensure availability throughout the application:

@NgModule({
declarations: [
AppComponent,
// other components
],
imports: [
BrowserModule,
ReactiveFormsModule,  // Add this line
// other modules
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

After importing ReactiveFormsModule, developers can create and manage reactive forms in your components.

Creating a Basic Reactive Form

Shaping a basic form requires developers first to understand the core concepts of Reactive forms.

Understanding Form Control and Form Group

In this approach, each model contains FormControl and FormGroup instances. FormControl is the most fundamental building block of Reactive forms, working as an individual input field. It holds the value users type into it, tracks any changes, and updates in real-time to reflect those changes. It also checks for the current validation status by determining whether a current input is required, valid, or invalid.

A FormGroup combines multiple FormControl into a single unit, like grouping several questions that are logically tied together. It tracks the same information as FormControl but for each control in the group.

They allow developers to manage different controls through one entity alone, making it easier to collect and validate data. They’re also very common in forms or form sections that contain information like first name, last name, address, and so on.

An example of building a form looks like:

1. Component (TypeScript)

import { Component } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
// Create a form group with two form controls (name and email)
userForm = new FormGroup({
name: new FormControl(''),
email: new FormControl('')
});// Log form values on submit
onSubmit() {
console.log(this.userForm.value);
}
}

2. Template (HTML)

<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
<label>Name:</label>
<input type="text" formControlName="name">  <label>Email:</label>
<input type="email" formControlName="email"><button type="submit">Submit</button>
</form>

Building a Simple Form

Building a simple form calls for binding form controls to HTML elements using formControlName and formGroup directives. formGroup groups multiple form controls together to manage them as a unit, while formControlName represents each input field as individual controls.

1. Component:

import { Component } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';@Component({
selector: 'app-root',
templateUrl: './app.component.html',
})
export class AppComponent {
// Define a form group with three controls: name, email, and password
userForm = new FormGroup({
name: new FormControl(''),
email: new FormControl(''),
password: new FormControl('')
});// Handle form submission
onSubmit() {
console.log(this.userForm.value);  // Logs the form values to the console
}
}

2. Bind form controls to HTML Elements:

<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
<label for="name">Name:</label>
<input id="name" type="text" formControlName="name">  <label for="email">Email:</label>
<input id="email" type="email" formControlName="email"><label for="password">Password:</label>
<input id="password" type="password" formControlName="password"><button type="submit">Submit</button>
</form><!-- Display form values dynamically for debugging -->
<p>Form Value: {{ userForm.value | json }}</p>

3. Form submission output:

{
"name": "John Doe",
"email": "[email protected]",
"password": "securePassword123"
}

Adding Validation to Reactive Forms

Apart from taking user information, Reactive forms can also determine whether that data is within expected parameters. Adding validation to reactive forms is a very streamlined process, providing a series of validators that ensure the received input follows the required guidelines.

Built-in Validators

Angular offers developers a wide variety of built-in validators.The most common options include required, minLength, maxLength, and pattern. These validators allow them to perform standard form validation, which is a significant benefit.

import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';@Component({
selector: 'app-root',
templateUrl: './app.component.html',
})
export class AppComponent {
// Define a form group with validators for name, email, and password
userForm = new FormGroup({
name: new FormControl('', [
Validators.required,          // Name is required
Validators.minLength(3),       // Minimum length of 3 characters
Validators.maxLength(20)       // Maximum length of 20 characters
]),
email: new FormControl('', [
Validators.required,          // Email is required
Validators.pattern('^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$') // Basic email pattern
]),
password: new FormControl('', [
Validators.required,          // Password is required
Validators.minLength(8),       // Minimum length of 8 characters
Validators.maxLength(15),      // Maximum length of 15 characters
Validators.pattern('^(?=.*[0-9])(?=.*[a-zA-Z])([a-zA-Z0-9]+)$') // At least one number and one letter
])
});// Handle form submission
onSubmit() {
if (this.userForm.valid) {
console.log(this.userForm.value);  // Log form values to console if form is valid
} else {
console.log('Form is invalid');
}
}// Helper method to get form control for validation checks
get formControls() {
return this.userForm.controls;
}
}

In HTML, this looks like:

<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
<label for="name">Name:</label>
<input id="name" type="text" formControlName="name">
<div *ngIf="formControls.name.errors && formControls.name.touched">
<small *ngIf="formControls.name.errors.required">Name is required.</small>
<small *ngIf="formControls.name.errors.minlength">Name must be at least 3 characters long.</small>
<small *ngIf="formControls.name.errors.maxlength">Name cannot exceed 20 characters.</small>
</div>  <label for="email">Email:</label>
<input id="email" type="email" formControlName="email">
<div *ngIf="formControls.email.errors && formControls.email.touched">
<small *ngIf="formControls.email.errors.required">Email is required.</small>
<small *ngIf="formControls.email.errors.pattern">Invalid email format.</small>
</div><label for="password">Password:</label>
<input id="password" type="password" formControlName="password">
<div *ngIf="formControls.password.errors && formControls.password.touched">
<small *ngIf="formControls.password.errors.required">Password is required.</small>
<small *ngIf="formControls.password.errors.minlength">Password must be at least 8 characters long.</small>
<small *ngIf="formControls.password.errors.maxlength">Password cannot exceed 15 characters.</small>
<small *ngIf="formControls.password.errors.pattern">Password must contain both letters and numbers.</small>
</div><button type="submit">Submit</button>
</form>

Once developers type “Validators” in their IDE, they’ll receive a list of all built-in validators that follow a defined pattern. Developers only need to call them if they pass additional configurations to them.

The Validators class also allows for grouping relevant validators into a unit. Despite their versatility, built-in validators sometimes aren’t enough to provide developers with necessary answers. In these situations, they’re better off creating their own custom validators.

Most validators are synchronous, meaning they immediately return an error (invalid input) or null (valid input) validation. To inform users about their input’s status, developers often conditionally display error messages. This feedback prompts users to quickly correct their input data submitting the form.

Custom Validators

Custom validators are functions that take control values as input with a response of either “null” if the control is valid, or an error object for invalid controls. They apply to any other validator in a form control or group.

Developers can create the custom validator in a component or separate file to compare two form fields.

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

export function passwordMatchValidator(controlName: string, matchingControlName: string): ValidatorFn {
return (formGroup: AbstractControl) => {
const control = formGroup.get(controlName);
const matchingControl = formGroup.get(matchingControlName);

if (!control || !matchingControl) {
return null; // Return null if controls are not found
}

if (matchingControl.errors && !matchingControl.errors['passwordMismatch']) {
// Return if another validator has already found an error on the matchingControl
return null;
}

if (control.value !== matchingControl.value) {
// Set the error on the matchingControl if validation fails
matchingControl.setErrors({ passwordMismatch: true });
} else {
// Clear the error if validation passes
matchingControl.setErrors(null);
}

return null;
};
}

An example of where custom validators are useful tools is within email confirmation inputs, which requires users to type their email a second time and must match the first input.

Working with Form Arrays

Handling a list of items in requires a dynamic approach through FormArray. Angular provides the adaptability to create forms that can expand and contract as needed.

Introduction to Form Arrays

FormArray is similar to FormGroup, combining different controls into a single unit. However, they don’t require naming. It’s particularly useful in forms where the user must input different email addresses and provide alternative phone numbers.

This Angular feature also provides the mechanism to dynamically add or remove controls from the interface. Like FormControl and FormGroup, it holds values, tracks changes, and manages validation status.

Building Dynamic Forms Using Form Arrays

FormArray allows developers to create a variable number of inputs within dynamic forms.

Setting up FormArray looks like:

import { Component } from '@angular/core';
import { FormGroup, FormArray, FormControl, Validators } from '@angular/forms';@Component({
selector: 'app-root',
templateUrl: './app.component.html',
})
export class AppComponent {
userForm = new FormGroup({
addresses: new FormArray([]) // Initialize FormArray
});// Get addresses FormArray
get addresses() {
return this.userForm.get('addresses') as FormArray;
}// Add a new address control
addAddress() {
this.addresses.push(new FormControl('', Validators.required));
}// Remove an address control
removeAddress(index: number) {
this.addresses.removeAt(index);
}onSubmit() {
console.log(this.userForm.value);
}
}

The HTML Template users dynamic rendering for inputs.

<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
<div formArrayName="addresses">
<div *ngFor="let address of addresses.controls; let i = index">
<input [formControlName]="i" placeholder="Enter address">
<button type="button" (click)="removeAddress(i)">Remove</button>
</div>
</div>  <button type="button" (click)="addAddress()">Add Address</button>
<button type="submit">Submit</button>
</form>

Handling Form Submission and Data Processing

The last step to successfully creating an Angular reactive form is to prepare it for submission and data processing.

Submitting Reactive Forms

Reactive form submissions require developers to define an (ngSubmit)method to handle the submission logic and then bind it to the element within the form component.

This method can validate the form, process its input data, and prepare it for server distribution. Developers often send form data to a server using Angular’s HttpClient service. To do so, they must ensure they import the HttpClientModule in their component’s module.

import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
// Initialize the form with controls
userForm = new FormGroup({
name: new FormControl('', Validators.required),
email: new FormControl('', [Validators.required, Validators.email])
});// On form submission
onSubmit() {
if (this.userForm.valid) {
console.log(this.userForm.value);  // Process form data
this.successMessage = "Form submitted successfully!";
} else {
this.errorMessage = "Please fill out the form correctly.";
}
}successMessage = '';
errorMessage = '';
}

HTML Template:

<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
<label>Name:</label>
<input type="text" formControlName="name">
<div *ngIf="userForm.controls.name.invalid && userForm.controls.name.touched">
Name is required.
</div>  <label>Email:</label>
<input type="email" formControlName="email">
<div *ngIf="userForm.controls.email.invalid && userForm.controls.email.touched">
Enter a valid email.
</div><button type="submit">Submit</button>
</form><!-- Feedback messages -->
<div *ngIf="successMessage">{{ successMessage }}</div>
<div *ngIf="errorMessage">{{ errorMessage }}</div>

Successful submissions should show a success message to the user, while forms with errors must indicate the field with the issue so users can edit.

Resetting and Clearing Form Data

After successfully submitting or canceling an operation, users expect forms to reset to their original state.

This process involves resetting the values within reset() as well as formControl and formGroup validation statuses to make the form pristine again. reset()with values resets the form with specific default values.

import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
// Define the form group with some initial values
userForm = new FormGroup({
name: new FormControl('', Validators.required),
email: new FormControl('', [Validators.required, Validators.email])
});successMessage = '';// Submit and reset form after successful submission
onSubmit() {
if (this.userForm.valid) {
console.log(this.userForm.value);  // Process form data
this.successMessage = "Form submitted successfully!";
this.resetForm(); // Reset the form after successful submission
}
}// Method to reset form
resetForm() {
this.userForm.reset();  // Reset form values and state
}// Optionally, reset form with specific values
resetFormWithDefaults() {
this.userForm.reset({
name: 'Default Name',
email: ''
});
}
}

HTML

<form [formGroup]="userForm" (ngSubmit)="onSubmit()">
<label for="name">Name:</label>
<input id="name" type="text" formControlName="name">
<div *ngIf="userForm.controls.name.invalid && userForm.controls.name.touched">
Name is required.
</div>  <label for="email">Email:</label>
<input id="email" type="email" formControlName="email">
<div *ngIf="userForm.controls.email.invalid && userForm.controls.email.touched">
Enter a valid email.
</div><button type="submit">Submit</button>
<button type="button" (click)="resetForm()">Reset Form</button>
</form><div *ngIf="successMessage">{{ successMessage }}</div>

Users must sometimes submit multiple forms, which requires form resetting and clearing. Once users fill out a form and submit it, the form should immediately clear back to its initial state so users can swiftly create a new one.

Advanced Topics in Reactive Forms

After mastering the basics of Reactive forms, developers can use more advanced techniques in their work.

Nested Form Groups

Forms are sometimes complex, especially when they contain a wide variety of fields with correlated values. In these situations, developers must find a way to organize and manage this data effectively.

Nested formGroup helps developers arrange interconnected data at different hierarchical levels.

For instance, users might need to input their street address in a nested form. Within that parameter, users might also need to provide a postal code, their state, and so on.

import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
// Define the main form with nested address FormGroup
userForm = new FormGroup({
personalInfo: new FormGroup({
firstName: new FormControl('', Validators.required),
lastName: new FormControl('', Validators.required),
}),
address: new FormGroup({
street: new FormControl('', Validators.required),
city: new FormControl('', Validators.required),
state: new FormControl('', Validators.required),
zipCode: new FormControl('', [
Validators.required,
Validators.pattern('^[0-9]{5}$') // 5-digit zip code validation
])
})
});// Handle form submission
onSubmit() {
if (this.userForm.valid) {
console.log(this.userForm.value);
}
}
}

Dynamic Form Validation

Angular allows developers to conditionally apply validation rules based on user input. This dynamic approach makes it easy to add or remove validation for a given field, depending on the values entered in other fields. This capability simplifies building complex forms by adjusting requirements on the fly.

For example, in a registration form for a scientific conference, both students and scientists may use the same form, but different fields are required based on the user’s selection. If the user selects “scientist,” a field for the working institution becomes required. If they select “student,” that field is either hidden or marked as optional.

Async Validators

Sometimes validation needs to be done asynchronously, such as when checking the availability of a username or performing server-side validation. In such cases, async validators are used. These validators are particularly useful for real-time, backend-driven checks that can’t be completed synchronously.

To apply async validators, developers must:

  1. Create the async validator to return an Observable or Promise.
  2. Apply it to a formControl using the validator asyncValidators property.
  3. Display appropriate messages based on the result of the async validation to provide user feedback.

Best Practices for Reactive Forms in Angular

Building dynamic and user-friendly reactive forms is crucial for Angular applications. Here are key best practices to keep forms maintainable and performant:

Separation of Concerns

Maintaining a clear Separation of Concerns (SoC) is essential. By keeping form logic, business logic, and view rendering separate, you create a more testable and maintainable codebase.

Tips for Separation of Concerns:

  • Apply the Single Responsibility Principle (SRP) – Each function must focus on one specific task.
  • Use clear and descriptive naming – Functions’ names should clearly reflect their purpose so that developers quickly understand their behavior without inspecting their implementation.
  • Encapsulate logic – Functions should group related logic within themselves and separate unrelated concerns.
  • Prioritize modularization and composition – Developers can divide complex tasks into smaller, more manageable functions, each focusing on a specific concern.

Performance Considerations

For forms handling large amounts of data or numerous form controls, performance can degrade if not properly managed. One common issue arises from real-time updates to the formControl values. By default, Angular updates the form value as the user types, which can slow down performance with many controls.

To optimize performance, developers can change the updateOn option to “blur”. This ensures that form updates only occur when the user moves away from a field, reducing the number of updates and improving overall performance in complex forms.

Accessibility Considerations

Ensuring forms are accessible is a core responsibility for developers. Reactive forms should be designed for users with disabilities, including those with mobility, vision, or cognitive challenges. To achieve this:

  • Use proper HTML semantics and provide clear, descriptive labels for each form input.
  • Apply ARIA attributes where necessary.
  • Ensure the form design follows best practices in color contrast and visual clarity.
  • Provide clear instructions and feedback, along with explicit calls to action.

Graduating from Angular Reactive Forms 101

Reactive forms are a go-to option for development teams for their versatility, reliability, and scalability. They provide better control over dynamic data, making them ideal for managing complex form states and validations.

To master reactive forms, developers should fully understand FormControl, FormGroup, and FormArray. Building and testing forms with various use cases will help developers gain confidence and refine their approach. Over time, this foundational knowledge will allow for seamless integration of more advanced Angular form features.

FAQ

What is the difference between reactive forms and template-driven forms in Angular?

Template-driven forms rely on directives within the HTML template and are easier to set up for simple forms. Reactive forms are more model-driven, where the form’s structure is defined in the component class. They offer more control, and better handling of complex logic and perform optimally with large-scale forms or heavy user interactions.

Can I use reactive forms for complex, nested forms?

Yes, reactive forms are well-suited for complex, nested forms. You can use FormGroup and FormArray to handle interconnected data across different hierarchical levels.

What are async validators, and when should I use them?

Async validators are used for asynchronous validation tasks, such as checking if a username or email is available via server requests. They are ideal when real-time feedback is required, especially for validation dependent on external systems or services.

Justice Erolin

By Justice Erolin

Responsible for translating the company vision into technical roadmaps, BairesDev CTO Justice Erolin plans and coordinates engineering teams to help their output meet the highest market standards. His management and engineering expertise help take BairesDev's capabilities to the next level.

Stay up to dateBusiness, technology, and innovation insights.Written by experts. Delivered weekly.

Related articles

Contact BairesDev
By continuing to use this site, you agree to our cookie policy and privacy policy.