Angular ~ Forms - rohit120582sharma/Documentation GitHub Wiki

Introduction

Angular provides two ways to work with forms: template-driven forms and reactive forms, the latter also sometimes called model-driven forms.

The two technologies belong to the @angular/forms library and share a common set of form control classes.

References

Reactive Forms Introduction


Template-Driven Forms

When we apply ngModel directive on a input field, Angular will create a FormControl object and associate it with the input field under the hood. If ngModel is used within a form tag, we need to set name attribute as well on input field to distinguish it from other control objects.

In Angular, we have one directive called ngForm which creates a top-level FormGroup instance and binds it to a form to track aggregate form value and validation status. If we have not set this directive on the form element in the template, Angular will apply it automatically under the hood. The ngForm directive exposes an output property ngSubmit which is used to handle a submit event of a form.

In Angular, we have ngModelGroup directive which creates and binds a FormGroup instance to a DOM element.


Reactive or Model-Driven Forms

Reactive forms are also referred to as model-driven forms.

With reactive forms, you create a tree of Angular form control objects in the component class and bind them to native form control elements in the component template.

With reactive forms, we can listen to form or control changes easily. Each form group or form control exposes a few events which we can subscribe to (e.g. statusChanges, valuesChanges, etc).

To submit the form, we just need to use the ngSubmit directive and add it to the form element.

In a model-driven form to reset the form, we just need to call the function reset() in our model.

Reactive forms are synchronous and easy for unit test.

Setup

In order to use a reactive form in an angular application, we need npm install @angular/forms npm package and import the ReactiveFormsModule in the application module.

import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';

@NgModule({
	imports: [FormsModule, ReactiveFormsModule]
	...
})
export class AppModule {
}

Building Blocks

We represent the form as a model composed of instances of FormGroups and FormControls.

On the form, we must declare [formGroup] as a binding, and formControlName as a directive with the corresponding Object key name.

Angular uses following fundamental block (Essential form classes and directives) to create an angular reactive form:

  • Classes

    • AbstractControl - It is the base class for the three concrete form control classes: FormControl, FormGroup, and FormArray. It provides their common behaviours, like running validators, calculating status, and resetting state. It also defines the properties that are shared between all sub-classes, like value, valid, and dirty. It shouldn't be instantiated directly.
    • FormControl - It is a class that tracks the value and validity state of an individual form control. It corresponds to an HTML form control such as an input box or selector. When instantiating a FormControl, you can pass in an initial value as the first argument. With this class, we can check following properties on control:
      • value
      • touched / untouched
      • dirty / pristine
      • valid / invalid
      • errors
    • FormGroup - It is a class that tracks the value and validity state of a group of FormControl instances. A FormGroup aggregates the values of each child FormControl into one object, with each control name as the key. When instantiating a FormGroup, pass in a collection of child controls as the first argument. The key for each child will be the name under which it is registered. The top-level form in our component is a FormGroup. FormGroup class also have same properties like FormControl.
    • FormArray - It is a class that tracks the value and validity state of a numerically indexed array of FormControl, FormGroup, and FormArray instances. It calculates its status by reducing the statuses of its children.
  • Directives

    • [formGroup] - Binds an existing FormGroup to a DOM element.
    • formControlName - Syncs a FormControl in an existing FormGroup to a form control element by name.
    • formGroupName - Syncs a nested FormGroup to a DOM element.
    • formArrayName - Syncs a nested FormArray to a DOM element.

Validators

Validators are rules which an input control has to follow. If the input doesn’t match the rule then the control is said to be invalid.

When building a reactive form, we don't use HTML 5 attributes on input element in the template for validation. Instead, we assign validators when creating a FormControl object.

A validator is a function that processes a FormControl or collection of controls and returns a map of errors. A null map means that validation has passed.

The first parameter of a FormControl constructor is the initial value of the control, we’ll leave that as an empty string. The second parameter contains either a single validator if we only want to apply one, or a list of validators if we want to apply multiple validators to a single control.

Angular comes with a Validators class that has some static methods (validators) built-in.

  • Validators.required
  • Validators.minLength(minLen: number)
  • Validators.maxLength(maxLen: number)
  • Validators.pattern(regex-pattern: string)

Form control state

The form control instance on our model encapsulates the state of the control itself such as:

  • Dirty & Pristine
    • dirty is true if the user has changed the value of the control otherwise, it’s false.
    • The opposite of dirty is pristine.
  • Touched & Untouched
    • A control is said to be touched if the user focused on the control and then focused on something else.
    • touched is true of the field has been touched by the user, otherwise it’s false.
    • The opposite of touched is the property untouched.
  • Valid & Invalid
    • valid is true if the field doesn’t have any validators or if all the validators are passing.
<!-- registration.html.ts -->
<form novalidate [formGroup]="myform">
	<div class="form-group">
		<label>First Name</label>
		<input type="text" class="form-control" formControlName="firstName">
		<div class="form-control-feedback" *ngIf="myform.controls.firstName.touched && myform.controls.firstName.invalid">
			<p *ngIf="myform.controls.firstName.errors.required">FirstName is required</p>
			<p *ngIf="myform.controls.firstName.errors.minlength">FirstName must be 5 characters long</p>
		</div>
	</div>
	<div class="form-group">
		<label>Last Name</label>
		<input type="text" class="form-control" formControlName="lastName">
		<div class="form-control-feedback" *ngIf="myform.get('lastName').touched && myform.get('lastName').invalid">
			<p *ngIf="myform.controls.lastName.errors.required">LastName is required</p>
			<p *ngIf="myform.controls.lastName.errors.minlength">LastName must be 5 characters long</p>
		</div>
	</div>
	<fieldset formGroupName="address">
		<div class="form-group">
			<label>Street</label>
			<input type="text" class="form-control" formControlName="street">
		</div>
		<div class="form-group">
			<label>Zip</label>
			<input type="text" class="form-control" formControlName="zip">
		</div>
		<div class="form-group">
			<label>City</label>
			<select class="form-control" formControlName="city">
				<option value="">Please select a city</option>
			</select>
		</div>
	</fieldset>
</form>
<pre>
	Dirty? {{ myform.controls.firstName.dirty }}
	Pristine? {{ myform.controls.firstName.pristine }}
	Touched? {{ myform.controls.firstName.touched }}
	Valid? {{ myform.controls.firstName.valid }}
</pre>
/* registration.controller.ts */
import { AbstractControl, FormGroup, FormControl, Validators } from '@angular/forms';

@Component({
	selector: 'rs-registration',
	...
})
class RegistrationComponent implements OnInit {
	registerForm: FormGroup;

	constructor(){
	}
	ngOnInit(){
		this.registerForm = new FormGroup({
			firstName: new FormControl('', [Validators.required, Validators.minLength(5)]),
			lastName: new FormControl('', [Validators.required, Validators.minLength(5)]),
			address: new FormGroup({
				street: new FormControl(),
				zip: new FormControl(),
				city: new FormControl(),
			})
		});
	}
}

FormBuilder

As mentioned earlier, the creation of our form model looks quite wordy as we have to call new FormGroup() and new FormControl several times to construct the model. Luckily, we’ve used rather low level APIs and we can use a higher level API that makes this task a bit more pleasant.

Angular has a utility class FormBuilder which is like a factory that creates FormGroup’s and FormControl’s for us. You have to inject a FormBuilder into the component constructor.

/* registration.controller.ts */
import { FormBuilder, AbstractControl, FormGroup, FormControl, FormArray, Validators } from '@angular/forms';

@Component({
	selector: 'rs-registration',
	...
})
export class RegistrationComponent implements OnInit {
	registerForm: FormGroup;

	constructor(private fb:FormBuilder){
	}
	ngOnInit(){
		this.registerForm = this.fb.group({
			firstname: ['', [Validators.required, Validators.minLength(5)]],
			lastname: ['', [Validators.required, Validators.minLength(5)]],
			address: this.fb.group({
				street: [],
				zip: [],
				city: []
			}),
			topics: this.fb.array([])
		});
	}
}
⚠️ **GitHub.com Fallback** ⚠️