in app.component import FormGroup and add property of type FormGroup
import { Component } from '@angular/core';
import { FormGroup } from '@angular/forms';
@Component({
...
})
export class AppComponent {
genders = ['male', 'female'];
signupForm: FormGroup;
}
n app.module import ReactiveFormsModule and add it to the imports property of NgModule
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
ReactiveFormsModule,
HttpModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Top
Index
Reactive: creating a form in code
in app.component import OnInit
in ngOnInit instantiate the FormGroup property, the c'tor arg is a JSON object
import FormControl
add a property named 'username' to the JSON object and instantiate it as a FormControl
FormControl's c'tor arguments
- initial value of form
- array of validators to be applied to the control (optional)
- array of async validators to be applied to the control (optional)
add properties for email and gender setting the gender's default value to male
import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
@Component({
...
})
export class AppComponent implements OnInit{
genders = ['male', 'female'];
signupForm: FormGroup;
ngOnInit(){
this.signupForm = new FormGroup({
'username' : new FormControl(null),
'email' : new FormControl(null),
'gender': new FormControl('male')
});
}
}
Top
Index
Reactive: syncing HTML and form
add directives to markup form tag
use property binding with FormGroup referencing the FormGroup created programmatically
add formControlName directive to the inputs in the markup with the control name
fron the code
<form [formGroup]="signupForm">
<div class="form-group">
<label for="username">Username</label>
<input
type="text"
id="username"
formControlName="username"
class="form-control">
</div>
<div class="form-group">
<label for="email">email</label>
<input
type="text"
id="email"
formControlName="email"
class="form-control">
</div>
<div class="radio" *ngFor="let gender of genders">
<label>
<input
type="radio"
formControlName="gender"
[value]="gender">{{ gender }}
</label>
</div>
<button class="btn btn-primary" type="submit">Submit</button>
</form>
Top
Index
Reactive: submitting the form
in the markup bind the form's ngSubmit property to an event handler
<form [formGroup]="signupForm" (ngSubmit)="onSubmit()">
...
</form>
in the code add the event handler
...
export class AppComponent implements OnInit {
...
signupForm: FormGroup;
ngOnInit() {
this.signupForm = new FormGroup({
'username': new FormControl(null),
'email': new FormControl(null),
'gender': new FormControl('male')
});
}
onSubmit() {
console.log(this.signupForm);
}
}
no local references are needed in the markup because the HTML is synchronized with
with the form created programmatically
Top
Index
Reactive: adding validation
import Validators
add required validator as username control's c'tor second arg
can also pass an array of Validators to the email control add the required and email
validators
import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
...
export class AppComponent implements OnInit {
...
ngOnInit() {
this.signupForm = new FormGroup({
'username': new FormControl(null, Validators.required),
'email': new FormControl(null, [Validators.email, Validators.required]),
'gender': new FormControl('male')
});
}
...
}
Top
Index
Reactive: getting access to controls
the form has a get method which takes a string as an arg
the string is the control name or the path & control name
use the method and ngIf to display/hide error messages
<form [formGroup]="signupForm" (ngSubmit)="onSubmit()">
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" formControlName="username" class="form-control">
<span
*ngIf="!signupForm.get('username').valid && signupForm.get('username').touched"
class="help-block">
Please enter a valid username.
</span>
</div>
<div class="form-group">
<label for="email">email</label>
<input type="text" id="email" formControlName="email" class="form-control">
<span
*ngIf="!signupForm.get('email').valid && signupForm.get('email').touched"
class="help-block">
Please enter a email address.
</span>
</div>
<div class="radio" *ngFor="let gender of genders">
<label>
<input
type="radio"
formControlName="gender"
[value]="gender">{{ gender }}
</label>
</div>
<span
*ngIf="!signupForm.valid && signupForm.touched"
class="help-block">
Please enter valid data.
</span>
</form>
<button class="btn btn-primary" type="submit">Submit</button>
edit the css adding a class which turns the inputs' border red when the control
has been touched and is not valid
.container {
margin-top: 30px;
}
input.ng-invalid.ng-touched {
border: 1px solid red;
}
Top
Index
Reactive: grouping controls
in app.component's ngOnInit method add a FormGroup and nest the username and email
FormControls within
...
export class AppComponent implements OnInit {
...
ngOnInit() {
this.signupForm = new FormGroup({
'userData': new FormGroup({
'username': new FormControl(null, Validators.required),
'email': new FormControl(null, [Validators.email, Validators.required])
}),
'gender': new FormControl('male')
});
}
...
}
in the app.component markup add a div with the formControlName attribute assigned
to the name of the new FormGroup
move the username and email form-groups inside the new div
app will fail because the ngIf statements are unable to locate the username and
email controls
to add the path to the ngIf statements use the dot operator prepended with the value
assigned to the formControlName attribute of the new div
<form [formGroup]="signupForm" (ngSubmit)="onSubmit()">
<div formGroupName="userData">
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" formControlName="username" class="form-control">
<span *ngIf="!signupForm.get('userData.username').valid && signupForm.get('userData.username').touched" class="help-block">Please enter a valid username.</span>
</div>
<div class="form-group">
<label for="email">email</label>
<input type="text" id="email" formControlName="email" class="form-control">
<span *ngIf="!signupForm.get('userData.email').valid && signupForm.get('userData.email').touched" class="help-block">Please enter a email address.</span>
</div>
</div>
...
</form>
Top
Index
Reactive: arrays of form controls (FormArray)
in app.component's ngOnInit below the gender control add an object named hobbies
of type FormArray
the FormArray's c'tor takes an array as an arg so pass an empty array
add a method named onAddHobby which creates a new FormControl and pushes the control
into the hobbies array
note the cast
import { Component, OnInit } from '@angular/core';
import { FormArray, FormControl, FormGroup, Validators } from '@angular/forms';
...
export class AppComponent implements OnInit {
...
ngOnInit() {
this.signupForm = new FormGroup({
'userData': new FormGroup({
'username': new FormControl(null, Validators.required),
'email': new FormControl(null, [Validators.email, Validators.required])
}),
'gender': new FormControl('male'),
'hobbies': new FormArray([])
});
}
onAddHobby() {
const control = new FormControl(null, Validators.required);
// cast object to FormArray before pushing control to array
(<FormArray>this.signupForm.get('hobbies')).push(control);
}
...
}
in the app.compoent markup add a div below the radio buttons
assign the FormArray name to the formArrayName directive
inside the div add a header and a button
assign the button's click event to the onAddHobby method
below the button add a div with the CSS class form-group
in the div tag add a ngFor directive to loop through the hobbies array
also introduce a local variable to which the current array index is set within the
newset div add an input of CSS class form-control
use property binding to bind formControlName to the current index of the array
<form [formGroup]="signupForm" (ngSubmit)="onSubmit()">
...
<div formArrayName="hobbies">
<h4>Your Hobbies</h4>
<button class="btn btn-default" type="button" (click)="onAddHobby()">Add Hobby</button>
<div
class="form-group"
*ngFor="let hobbyControl of signupForm.get('hobbies').controls; let i = index">
<input type="text" class="form-control" [formControlName]="i">
</div>
</div>
...
</form>
clicking the Add Hobby button dynamically creates an input control where the user
can enter a hobby
Top
Index
Reactive: creating custom validators
example considers preventing users from selecting certain user names
a validator is just a function which gets executed automatically by Angular
method arg is the control to be validated
return value is a JSON object containing key value pair where value is a boolean
if validation is successful return null
change the second arg of the FormControl c'tor for username to an array and add
the new validator
because Angular invokes the method an error will occur because the this pointer
is an unknown
bind the this pointer to the validator
at this point a name in the array is considered valid because indexOf returns -1
if the name is not found because Angular considers -1 to be true
to make comparison work check that indexOf is not the same as -1
...
export class AppComponent implements OnInit {
...
forbiddenUsernames = ['Chris', 'Anna'];
ngOnInit() {
this.signupForm = new FormGroup({
'userData': new FormGroup({
'username': new FormControl(null, [Validators.required, this.forbiddenNames.bind(this)]),
...
}),
...
});
}
...
forbiddenNames(control: FormControl): { [s: string]: boolean } {
if (this.forbiddenUsernames.indexOf(control.value) !== -1) {
return { 'name is forbidden': true };
}
return null;
}
}
Top
Index
Reactive: using error codes
now two different errors are possible with the username
- username is required
- username is invalid
remove error message within the span in username form-group
add two spans within the span where the error message was
the two spans check the control's error code to determine which error message is
shown
<form [formGroup]="signupForm" (ngSubmit)="onSubmit()">
<div formGroupName="userData">
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" formControlName="username" class="form-control">
<span *ngIf="!signupForm.get('userData.username').valid && signupForm.get('userData.username').touched" class="help-block">
<span *ngIf="signupForm.get('userData.username').errors['nameIsForbidden']">The username is invalid.</span>
<span *ngIf="signupForm.get('userData.username').errors['required']">This field is required.</span>
</span>
</div>
...
</div>
...
</form>
Top
Index
Reactive: creating a custom async validator
add method to validate email addresses
function returns a Promise or an Observable
the timeout represents the time used to send a value down the wire and receive a
response
if control's value is invalid the resolve method's arg is a JSON object otherwise
the arg should be null
while the async method runs the control's validity will be pending
...
export class AppComponent implements OnInit {
...
ngOnInit() {
this.signupForm = new FormGroup({
'userData': new FormGroup({
'username': new FormControl(null, [Validators.required, this.forbiddenNames.bind(this)]),
'email': new FormControl(null, [Validators.email, Validators.required], this.forbiddenEmails)
}),
'gender': new FormControl('male'),
'hobbies': new FormArray([])
});
}
...
forbiddenEmails(control: FormControl): Promise
| Observable {
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
if(control.value === '[email protected]') {
resolve({'emailIsForbidden': true});
} else {
resolve(null);
}
}, 1500);
});
return promise;
}
}
Top
Index
Reactive: reacting to status or value changes
can subscribe to valueChanges of form or individual controls
can subscribe to statusChanges of form or individual controls
...
export class AppComponent implements OnInit {
...
ngOnInit() {
this.signupForm = new FormGroup({
'userData': new FormGroup({
'username': new FormControl(null, [Validators.required, this.forbiddenNames.bind(this)]),
'email': new FormControl(null, [Validators.email, Validators.required], this.forbiddenEmails)
}),
'gender': new FormControl('male'),
'hobbies': new FormArray([])
});
this.signupForm.valueChanges.subscribe(
(value) => console.log(value)
);
this.signupForm.statusChanges.subscribe(
(status) => console.log(status)
);
}
...
}
Top
Index
Reactive setting and patching values
setValue, patchValue, and reset methods work the same in both reactive and template-driven
forms
...
ngOnInit() {
...
this.signupForm.valueChanges.subscribe(
(value) => console.log(value)
);
this.signupForm.statusChanges.subscribe(
(status) => console.log(status)
);
this.signupForm.setValue({
'userData': {
'username': 'flip',
'email': '[email protected]'
},
'gender': 'male',
'hobbies': []
});
this.signupForm.patchValue({
'userData': {
'username': 'flipper'
}
});
}
...
Top
Index