30 June 2019
Angular (17) -- Reactive Forms
by Jerry Zhang
Day 25: Reactive Forms
Setup
- Create an attribute of type
FormGroup
. - Import
ReactiveFormsModule
in the app.module.
Create a new form with typescript
Create a new FormGroup
and some FormControl
ngOnInit() {
this.signupForm = new FormGroup({
'username': new FormControl(null),
'email': new FormControl(null),
'gender': new FormControl('male')
});
}
Connect the HTML form with our signupForm
<form [formGroup]="signupForm">
<input
type="text"
id="username3"
formControlName="username"
class="form-control">
First, we bind the formGroup
property with the form.
Second, we specify the formControlName
as the same name in the typescript file.
Submitting the Form
To submit the form, we still need to bind the ngSubmit
event with the form like before.
<form [formGroup]="signupForm" (ngSubmit)="onSubmit()">
However, this time, we do not need a reference to our form, because we have created the form in the typescript file. To access it, simply use the created attribute.
onSubmit() {
console.log(this.signupForm);
}
Validation
In the Template-driven approach, we added a required
on the input. This does not work in the reactive approach.
Instead, we add a second parameter in the FormControl constructor.
ngOnInit() {
this.signupForm = new FormGroup({
'username': new FormControl(null, Validators.required),
'email': new FormControl(null, [Validators.required, Validators.email]),
'gender': new FormControl('male')
});
}
Getting Access to Controls
We use the signupForm
attribute to get access to the form directly.
<span
*ngIf="!signupForm.get('username').valid && signupForm.get('username').touched"
class="help-block">Please enter a valid username!</span>
<span
*ngIf="!signupForm.get('email').valid && signupForm.get('email').touched"
class="help-block">Please enter a valid email!</span>
<span
*ngIf="!signupForm.valid && signupForm.touched"
class="help-block">Please enter valid data!</span>
Grouping Controls
We can put a FormGroup inside a FormGroup, and then put form controls in it.
ngOnInit() {
this.signupForm = new FormGroup({
'userData': new FormGroup({
'username': new FormControl(null, Validators.required),
'email': new FormControl(null, [Validators.required, Validators.email]),
}),
'gender': new FormControl('male')
});
}
To update the corresponding html file, we need to wrap the two controls in another div, and add a directive
formGroupName
.
<div formGroupName="userData">
Further more, we have to pass the path in the get method
<span
*ngIf="!signupForm.get('userData.username').valid && signupForm.get('userData.username').touched"
class="help-block">Please enter a valid username!</span>
FormArray
Create a new button. Make the type button
so that it does not accidentally submit the form.
<button class="btn btn-light" type="button">Add Hobby</button>
Then, when the user clicks this button, I want to dynamically add a control to my form.
- Step 1: add a new click event for this button
<button
class="btn btn-light"
type="button"
(click)="onAddHobby()">Add Hobby</button>
- Step 2: Add a new FormArray called ‘hobbies’ in the
signupForm
.
ngOnInit() {
this.signupForm = new FormGroup({
'userData': new FormGroup({
'username': new FormControl(null, Validators.required),
'email': new FormControl(null, [Validators.required, Validators.email]),
}),
'gender': new FormControl('male'),
'hobbies': new FormArray([])
});
}
- Step 3: Push a new control into the array
onAddHobby() {
const control = new FormControl(null, Validators.required);
(this.signupForm.get('hobbies') as FormArray).push(control);
}
- Step 4: Synchronize with HTML code. Loop the array and add the input.
<div formArrayName="hobbies">
<h4>Your Hobbies</h4>
<button
class="btn btn-light"
type="button"
(click)="onAddHobby()">Add Hobby</button>
<div
class="form-group"
*ngFor="let hobbyControl of getHobbies(); let i = index">
<input type="text" class="form-control" [formControlName]="i">
</div>
</div>
getHobbies() {
return (this.signupForm.get('hobbies') as FormArray).controls;
}
Notice that we need to specify the formArrayName in the outside div.
Also notice that we need to get the index for a temporary name of the newly created control.
Create Custom Validators
For example, we want to forbidden some specific user names.
forbiddenUsernames = ['Chris', 'Anna'];
- A validator is just a function.
forbiddenNames(control: FormControl): {[s: string]: boolean} {
if (this.forbiddenUsernames.indexOf(control.value) !== -1) {
return {'nameIsForbidden': true};
}
return null;
}
A validator should return an Object if it is valid, or null if it is invalid.
- Then, we add this function in the FormControl constructor.
'username': new FormControl(null, [Validators.required, this.forbiddenNames.bind(this)]),
Using Error Codes
<span *ngIf="signupForm.get('userData.username').errors['nameIsForbidden']">This name is invalid!</span>
<span *ngIf="signupForm.get('userData.username').errors['required']">This field is required!</span>
Creating a Custom Async Validator
forbiddenEmails(control: FormControl): Promise<any> | Observable<any> {
const promise = new Promise<any>((resolve, reject) => {
setTimeout(() => {
if (control.value === 'test@test.com') {
resolve({'emailIsForbidden': true});
} else {
resolve(null);
}
}, 1500);
});
return promise;
}
'email': new FormControl(null, [Validators.required, Validators.email], this.forbiddenEmails),
Reacting to status or value changes
this.signupForm.valueChanges.subscribe(
(value) => console.log(value)
);
this.signupForm.statusChanges.subscribe(
(status) => console.log(status)
);
Setting, Patching, and Resetting Values
this.signupForm.setValue({
'userData': {
'username': 'Max',
'email': 'sdf@test.com'
},
'gender': 'male',
'hobbies': []
});
this.signupForm.patchValue({
'userData': {
'username': 'Anna'
}
});
this.signupForm.reset();
Do not forget to add ReactiveFormModule in the app.module!!!
Pattern validator
<input
type="number"
id="amount"
class="form-control"
name="amount"
ngModel
required
pattern="^[1-9]+[0-9]*$"
>
tags: Angular