Angular Forms

Form Types in Angular

Angular provides two ways of building and managing forms: template-driven and reactive.

Template-driven forms are created primarily in a component’s template (minimal code in the component’s class). In contrast, reactive forms are created mainly in a component’s class (minimal code in the component’s template).

Choosing which type is best depends on the following:

  • the source of truth (template or class)
  • data mutability (mutating data model directly or not)
  • data flow (template to view or view to template)
  • personal preference
<!-- template-driven -->
<form #userForm="ngForm">
<label for="name">Name:</label>
<input id="name" type="text" name="name" required [(ngModel)]="user.name">
</form>
// reactive
import { FormGroup, FormControl, Validators } from '@angular/forms';
export class UserComponent {
userForm = new FormGroup({
name: new FormControl('', Validators.required)
});
// rest of class
}

Template-Driven Forms

In Angular, template-driven forms are created primarily in the template using the FormsModule (@angular/forms) and HTML <form> and <input> elements.

The FormsModule provides the NgForm and NgModel directives to manage a form. The FormsModule automatically attaches a NgForm directive to <form> elements in the template, which manages the overall form state.

The NgModel directive is two-way bound to an <input> (form control) element to “connect” it to a data model in the component. This data model is updated with any changes to the form control value.

<!-- UserComponent template -->
<form #userForm="ngForm">
<label for="name">Name:</label>
<!-- connected to user model in class -->
<input id="name" type="text" name="name" [(ngModel)]="user.name" required>
</form>

Reactive Forms

In Angular, reactive forms are created primarily in a component’s class using the ReactiveFormsModule (@angular/forms) and its utility classes: FormGroup and FormControl.

The overall form structure and the initial state are created in the class using FormGroup and FormControl. FormGroup contains the overall form structure and state and maintains a set of FormControl instances. A FormControl instance manages the state of a single form control. This form structure is then connected to the template using the formGroup and formControlName directives.

// others imports
import { ReactiveFormsModule, FormGroup, FormControl } from "@angular/forms"
@Component({
// other configurations
imports: [ReactiveFormsModule] // imports reactive forms module
})
export class UserComponent {
// create form structure using FormGroup
userForm = new FormGroup({
// create a form control with initial value using FormControl
name: new FormControl("")
})
// rest of class
}

NgForm and NgModel

In Angular, template-driven forms use the NgForm and NgModel directives (@angular/forms).

The NgForm directive is automatically applied to <form> elements by the FormsModule (when imported into the component) and manages the overall state of the form and its controls.

The NgModel directive is two-way bound to a data model in the class and should be used with the name attribute. NgModel registers the control within the NgForm instance and manages the state of the form control.

<!-- UserComponent template -->
<form #userForm="ngForm"> <!-- ngForm instance referenced through template variable -->
<label for="name">Name:</label>
<input
id="name"
type="text"
name="name"
[(ngModel)]="user.name" required>
<!-- name attribute needed for proper registration -->
<!-- ngModel registers the control within userForm -->
</form>

Template-Driven Validation

In Angular, validation can be added to template-driven form controls using the built-in HTML validation attributes like required and minlength. Angular provides more specific validation using directives like email. Angular automatically picks up the validation and updates the NgModel state.

Errors can be evaluated using the NgModel errors property.

<!-- UserComponent template -->
<form #userForm="ngForm">
<div>
<label for="name">Name:</label>
<!-- #name stores NgModel instance in template variable -->
<!-- required built-in HTML validation -->
<!-- minlength built-in HTML validation -->
<input
id="name"
type="text"
name="name"
[(ngModel)]="user.name"
required
minlength="5"
#name="ngModel"
>
<!-- conditionally render error message -->
@if(name.errors?.['required']) {
<span>Name is required!</span>
}
</div>
<div>
<label for="email">Email:</label>
<!-- required built-in HTML validation -->
<!-- email Angular directive validation -->
<!-- #email stores NgModel instance in template variable -->
<input
id="email"
type="email"
name="email"
[(ngModel)]="user.email"
required
email
#email="ngModel"
>
<!-- conditionally render error message -->
@if(email.errors?.['email']) {
<span>Valid email required!</span>
}
</div>
</form>

Form Validation Styling in Angular

In Angular, both template-driven and reactive forms have special CSS classes applied to the form control depending on its state (touched, valid, submitted, etc.).

These classes can apply styling to the form controls in specific states, such as valid or invalid.

input.ng-valid.ng-touched {
/* apply green border when input is valid and touched */
border-color: green;
}
input.ng-invalid.ng-touched {
/* apply red border when input is invalid and touched */
border-color: red;
}

FormGroup in Angular

In Angular, FormGroup (@angular/forms) is a utility class used to manage a set of FormControl instances.

A FormGroup is created using an object where the keys are the names of the form controls and the values are the FormControl instances.

// others imports
import { ReactiveFormsModule, FormGroup, FormControl, Validators } from "@angular/forms"
@Component({
// other configurations
imports: [ReactiveFormsModule]
})
export class UserComponent {
// create form structure using FormGroup
userForm = new FormGroup({
name: new FormControl("", Validators.required),
email: new FormControl("", Validators.email)
})
// rest of class
}

FormGroup and FormControl API

In Angular, the FormGroup and FormControl APIs can be used to manage the values, states, and errors of a form control(s).

The FormGroup API is used to manage the overall form state and errors and to get specific FormControl instances.

The FormControl API manages the state, errors, and value of a single FormControl.

// others imports
import { ReactiveFormsModule, FormGroup, FormControl, Validators } from "@angular/forms"
@Component({
// other configurations
imports: [ReactiveFormsModule]
})
export class UserComponent {
// create form structure using FormGroup
userForm = new FormGroup({
// create FormControl with required validation
name: new FormControl("", Validators.required),
// create FormControl with email and required validation
email: new FormControl("", [Validators.email, Validators.required])
})
hasError(controlName: string, errorType: string) {
const control = this.userForm.get(controlName)
if(control?.valid) {
return false
}
// report value for error
console.log(control.value)
// access error
return control.touched && control.errors?.[errorType]
}
submitForm() {
// check overall form state
if(this.userForm.valid) {
// process form values
console.log(this.userForm.value)
} else {
if(this.userForm.get('name').errors) {
// report errors for name
}
if(this.userForm.get('email').errors) {
// reports errors for email
}
}
}
// rest of class
}

FormGroup in Reactive Form Template

When working with reactive forms in Angular, the <form> element needs to be connected to a FormGroup instance in the template using the formGroup directive.

formGroup (from ReactiveFormsModule) is a property bound to the FormGroup instance and takes over synchronizing the <form> element state.

import { ReactiveFormsModule, FormGroup } from "@angular/forms"
@Component({
// ...
imports: [ReactiveFormsModule]
})
export class UserComponent {
userForm = new FormGroup({
//FormControls
});
}
<!-- bind to `FormGroup` -->
<form [formGroup]="userForm">
<!-- controls -->
</form>

FormControl in Reactive Form Template

When working with reactive forms in Angular, form controls in the template are connected to their FormControl instance in an enclosing FormGroup using the formControlName directive.

formControlName (from ReactiveFormsModule) is added to a form control within a FormGroup to let the FormControl take over managing the control state.

<!-- bind to FormGroup -->
<form [formGroup]="userForm">
<label for="name">Name:</label>
<!-- connects form control to the name FormControl instance -->
<input id="name" type="text" formControlName="name">
</form>

Validators Class in Reactive Forms

The Validators class (@angular/forms) provides validation functions that can be passed to FormControl instances when creating them.

Some validators like Validators.required and Validators.email can be used without configuration, while others like Validators.pattern() and Validators.minLength() need parameters to create the validator function.

// others imports
import { ReactiveFormsModule, FormGroup, FormControl, Validators } from "@angular/forms"
@Component({
// other configurations
imports: [ReactiveFormsModule]
})
export class UserComponent {
// create form structure using FormGroup
userForm = new FormGroup({
// create FormControl with required validation
name: new FormControl("", Validators.required),
// create FormControl with email, required, pattern validation
email: new FormControl("", [Validators.email, Validators.required, Validators.pattern(/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/)])
})
// rest of class
}

FormBuilder Service in Angular

The FormBuilder service provides a “syntactic sugar” API for creating reactive forms. This service simplifies creating forms by reducing the boilerplate code needed using its .group() method, which creates a FormGroup instance.

// others imports
import { ReactiveFormsModule, Validators, FormBuilder } from "@angular/forms";
import { Component, inject } from "@angular/core";
@Component({
// other configurations
imports: [ReactiveFormsModule]
})
export class UserComponent {
formBuilder = inject(FormBuilder) // inject FormBuilder
// create form structure using FormGroup
userForm = this.formBuilder.group({
name: ['', Validators.required],
email: ['', [
Validators.required,
Validators.email,
Validators.pattern(/[^!-]/)
]]
});
// rest of class
}

FormControl in Angular

In Angular, FormControl (@angular/forms) is a utility class for managing a single form control.

A FormControl is created using the initial value of the form control and any Validators functions to apply.

// others imports
import { ReactiveFormsModule, FormGroup, FormControl, Validators } from "@angular/forms"
@Component({
// other configurations
imports: [ReactiveFormsModule]
})
export class UserComponent {
// create form structure using FormGroup
userForm = new FormGroup({
// create FormControl with required validation
name: new FormControl("", Validators.required),
// create FormControl with `email` and required validation
email: new FormControl("", [Validators.email, Validators.required])
})
// rest of class
}

Learn more on Codecademy