Angular ~ Forms - rohit120582sharma/Documentation GitHub Wiki
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.
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 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.
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 {
}
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
, andFormArray
. 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. AFormGroup
aggregates the values of each childFormControl
into one object, with each control name as the key. When instantiating aFormGroup
, 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 aFormGroup
.FormGroup
class also have same properties likeFormControl
. -
FormArray - It is a class that tracks the value and validity state of a numerically indexed array of
FormControl
,FormGroup
, andFormArray
instances. It calculates its status by reducing the statuses of its children.
-
AbstractControl - It is the base class for the three concrete form control classes:
-
Directives
-
[formGroup] - Binds an existing
FormGroup
to a DOM element. -
formControlName - Syncs a
FormControl
in an existingFormGroup
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.
-
[formGroup] - Binds an existing
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)
The form control instance on our model encapsulates the state of the control itself such as:
- Dirty & Pristine
-
dirty
istrue
if the user has changed the value of the control otherwise, it’sfalse
. - The opposite of
dirty
ispristine
.
-
- Touched & Untouched
- A control is said to be touched if the user focused on the control and then focused on something else.
-
touched
istrue
of the field has been touched by the user, otherwise it’sfalse
. - The opposite of
touched
is the propertyuntouched
.
- Valid & Invalid
-
valid
istrue
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(),
})
});
}
}
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([])
});
}
}