In this section we will learn the basics of reactive forms in angular. We will learn how to set reactive forms up, use built-in validators and display validation errors.
Before we start there are some things to know. Ractive forms are fully functional without any HTML. All of the form depending logic is placed in the conponent as code and not in the template. This make our html very easy to read and edit. We can move parts of the HTML without break form related logic.
To be able to use reactive forms you have to import the related Module.
Open your app.module.ts
and add ReactiveFormsModule
to your imports:
// app/app.module.ts
...
import { ReactiveFormsModule } from '@angular/forms';
...
@NgModule({
...
imports: [
...
ReactiveFormsModule
],
...
})
export class AppModule {
}
After this we can try to check if everything is working fine by using the FormGroup
as the type of a class property in flight-edit.component.ts
.
We will use the property editForm
to test this. We add the FormGroup
as a type of the editForm
property.
// app/pages/edit-flight/edit-flight.component.ts
...
import {FormGroup} from '@angular/forms';
@Component({
...
})
export class FlightEditComponent implements OnInit {
editForm: FormGroup;
...
}
If no error occurs we did everything right and are ready to go on.
In our ngOnInit
method we start to create our editForm.
We create a new FormControl
and initialize it with 1 and assign it to a const called idControl
.
We also create a new FormGroup
.
As constructor parameter we insert an object with the key id
and use the created contorl idControl
as value and assign it to the property editForm
.
// app/pages/edit-flight/edit-flight.component.ts
...
import {FormGroup} from '@angular/forms';
@Component({
...
})
export class FlightEditComponent implements OnInit {
...
ngOnInit(): void {
const idControl = new FormControl(1);
this.editForm = new FormGroup({id: idControl});
}
}
To test it we log the formsGroups value to the console.
// app/pages/edit-flight/edit-flight.component.ts
...
ngOnInit(): void {
...
console.log(this.editForm.value);
}
...
There is a angular built-in service called FormBuilder
. This service allows us to set up a form without all the instanciation of controls.
We start to use the FormBuilder by importing it into our constructor.
// app/pages/edit-flight/edit-flight.component.ts
import {... FormBuilder} from '@angular/forms';
@Component({
...
})
export class FlightEditComponent implements OnInit {
...
constructor(private fb: FormBuilder) {
}
...
}
Next we replace the code for formEdit in our ngOnInit method and use the FormBuilder
to create the FormGroup
.
We also create controls for the rest of the flight properties.
// app/pages/edit-flight/edit-flight.component.ts
export class FlightEditComponent implements OnInit {
...
ngOnInit() {
this.editForm = this.fb.group({
id: [1],
from: [],
to: [],
date: []
});
console.log(this.editForm.value);
}
...
}
In the console we should see the id with the value 1
property as well as all other new properties with the value null.
As reactive forms are fully functional without any template we can also check all other states of the form i.e. valid
, touched
, pristine
.
// app/pages/edit-flight/edit-flight.component.ts
export class FlightEditComponent implements OnInit {
...
ngOnInit() {
...
console.log(this.editForm.valid);
console.log(this.editForm.touched);
console.log(this.editForm.pristine);
}
...
}
As a result in we should see true
for the valid
property, false
for touched
and true
for pristine
.
To connect a reactive form to a template we need the formGroup
and the formControlName
directive.
We apply the formGroup
to the form tag and bind the editFrom property to it
. Next we use the formControlName
directive and assign the controls name as a string to it. i.e. id
.
To check everything we add the controls value below the control. We access the formcontrol over the .controls
property or the .get()
method. As accessing the value over the .control
property is more to type: editForm?.controls?.from.value
we use the .get()
method instead: editForm.get('from')
<!-- app/pages/edit-flight/edit-flight.component.html -->
<form [formGroup]="editForm">
<input formControlName="id">
id: {{editForm.get('id').value}}
<input formControlName="from">
from: {{editForm.get('from').value}}
<input formControlName="to">
to: {{editForm.get('to').value}}
<input formControlName="date">
date: {{editForm.get('date').value}}
</form>
If everything works fine you should be able to assigne data to the control and update it by typing into the inputbox. The result should also visible below the control.
Angular has some built in reflection of the forms and controls state into the related html elements.
If you take a look at you forms html you will see the states for valid/invalid, pristine/dirty and touched/untouched reflected into the emelents hhtml as css classes.
<form class="ng-pristine ng-valid ng-touched" ...>
The same classes are also reflected for the forms control state.
<input class="ng-pristine ng-valid ng-touched" ...>
You can also access the controls state by access it over the form.controls
property.
<!-- app/pages/edit-flight/edit-flight.component.ts -->
<input formControlName="from">
from.value: {{editForm.get('from').value}}<br>
from.valid: {{editForm.get('from').valid}}<br>
from.touched: {{editForm.get('from').touched}}<br>
from.pristine: {{editForm.get('from').pristine}}<br>
...
This should print the controls value and it's states to the screen.
One last unique feature to reactive froms you should know is that they expost their value changes and status changes over an observable.
Let's set this up and subscribe to it. In your ngOnInit
method insert following:
// app/pages/edit-flight/edit-flight.component.ts
...
ngOnInit(): void {
...
this.editForm
.valueChanges
.subscribe(
(value => console.log('editForm value:', value))
);
this.editForm
.statusChanges
.subscribe(
(status => console.log('editForm status:', status))
);
this.editForm
.get('id')
.valueChanges
.subscribe(
(value => console.log('id control value:', value))
);
this.editForm
.get('id')
.statusChanges
.subscribe(
(status => console.log('id control status:', status))
);
}
...
If we type into the "id" control we should see logs for the controls and forms value and state changes.
Angular ships with a set of default validators. They are related to the basic html5 built in form validations. In reactive forms they are implemented as pure functions and you can use them by importing the Validators class. The validators class contains all default validators as static methods.
Let's implement some default validators to our from control. First we import the Validators class into our file. Now we can access the Validators class static methods. We apply validators as the second argument in the array in form group.
// app/pages/edit-flight/edit-flight.component.ts
...
import {... Validators} from '@angular/forms';
...
ngOnInit(): void {
this.editForm = this.fb.group({
id: [1, Validators.required],
...
});
}
...
If we want to use multiple validators for one control we put them in an array instead of inserting them directly:
// app/pages/edit-flight/edit-flight.component.ts
...
ngOnInit(): void {
this.editForm = this.fb.group({
id: [1, Validators.required],
from: [null, [Validators.required, Validators.minLength(3)]],
to: [null, [Validators.required, Validators.minLength(3)]],
date: [null, [Validators.required]]
});
}
...
To check and display errors there are some methods that you can use.
With the .errors
property you receive an object containing all errors.
<!-- app/pages/edit-flight/edit-flight.component.html -->
<input formControlName="from">
errors: {{editForm.get('from').errors | json}}
...
You can check for a specific error of a control over the .hasError()
method, and get the value of a specific error over the .getError()
method.
<!-- app/pages/edit-flight/edit-flight.component.html -->
<input formControlName="from" ...>
<div class="text-danger" *ngIf="editForm.get('from').hasError('minlength')">
A min length of {{editForm.get('from').getError('minlength').requiredLength}} is required
</div>
...