- Clone or Download the project/app from Github or any other sources
- If using Visual Studio Code / Insiders, open Command panel/terminal from menu: View -> Terminal (shortcut key is
CTRL + BackTick
ORCOMMAND + J
) - Go inside the project/app directory, command:
cd _examples-angular-templateDrivenForm OR cd templateDrivenForm
- Run command:
npm install
to install project/app dependencies(node_modules)
- To Build and run Angular App, command:
ng serve / npm start
ORng serve -o
ORng serve --open
- To change the port from 4200 to other port - type command:
ng serve --port 5000
- To check the application in browser type path/URL:
localhost:4200 / 5000
- Angular reactive forms, also known as model-driven forms, offers an easy way to use reactive programming styles-patterns and validations
- Reactive forms are forms where we write logic, validations, controls in the component's class
- It is flexible and can be used to handle complex form scenarios and large forms
- We write more component code and less HTML code which make unit testing easier
- Code and Logic resides in the
component class
(Template Driven Forms focus mainly on HTML template) No Two Way Data Binding
- (we need to react to user inputs to update the values, also some inbuilt angular methods are available to update component class)
- Reactive forms are mainly
used/well suited for complex scenarios
:Dynamic (On the Fly creation)
form fields- Initially only one field, click on add button new forms/fields created dynamically (+ Add Product, + Add Friend list, + Add Permanent & temporary address, etc.)
Custom Validation (Crossfield validations)
- Password & Confirm Password validation, old & new password/pin validation etc.Dynamic validation
- If subscribed to notification than email field is mandatory, hierarchy/dependency based scenarios, If Married enter spouse details, etc.
Unit test
- As logic is present in component class (Template Driven Forms we cant unit test HTML templates)
- Create & use new
Angular CLI
generated project - Add form HTML template/markup
- Create a form model by using
FormGroup
andFormControl
classes - Manage form control data/values
FormBuilder
Service (a simpler way to specify/manage form model)Validation implementation
- Simple, Custom, Cross-field, and Dynamic validations- Add
Dynamic form controls
- Submit the form data to
server
- First check
angular cli
installed version details on machine by using command at command prompt:ng -v
orng --version
Image - Angular CLI version
- If
angular CLI
not installed/available on machine (no version or error message displayed) then install it by using the command:npm install -g @angular/cli@latest
- To
update/upgrade angular CLI
to the latest version, use following commands in sequence:- command:
npm uninstall -g @angular/cli
- command:
npm cache verify or npm cache clean
- command:
npm install -g @angular/cli@latest
- command:
- Generate/create a new Angular app/project with Angular CLI - for dealing with angular forms with the syntax:
ng new appName
orng new project-name
, command:ng new angular-forms-reactivemodeldriven
(after creation check the newly generated folder structure)
Image - Angular project/app folder structure
- To run/serve application, at command prompt type command:
ng serve
orng serve --port 5000
orng serve --port 5000 -o
(--port flag
- to change the port number and-o or --open flag
- to automatically launch/open app in browser) - Go to the browser and load the application by typing address:
http://localhost:4200/
orhttp://localhost:5000/
- Add the
Bootstrap
framework to an application (CSS framework used to make cool/intuitive User Interface and look/feel)- Download bootstrap css to local machine from bootstrap website: https://getbootstrap.com/docs/4.1/getting-started/download/ into folder
assets/library/bootstrap.min.css
- Include bootstrap in application - index.html under
head
tag -<link rel="stylesheet" href="./assets/library/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous" />
- or you can include a
CDN
path in index.html under head tag - or else you can install bootstrap with npm command:
npm install bootstrap
and use it
- Download bootstrap css to local machine from bootstrap website: https://getbootstrap.com/docs/4.1/getting-started/download/ into folder
Image - Bootstrap website - installation options
- To verify bootstrap included/working properly in an application, check in Browser fonts, etc changed or not?
- Also in
app.component.html
just create any simple component like buttons or divs with bootstrap class:<button class="btn btn-success">Success Button</button>
or<div class="lead">Lead Heading</div>
- Right click on element and check in
inspect element
the bootstrap class and properties applied to respective elements
- Also in
Syntax & Example: index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>ReactiveModelDrivenForms</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="./assets/library/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous" />
</head>
<body>
<app-root></app-root>
</body>
</html>
- In file
app.component.html
create a registration form- Use bootstrap classes like
form-group
andform-control
class with div and input field respectively to create form fields with standard look and feel - Create a user-name and email
input fields
- Create a password and confirm
password fields
- Create a submit button named
Register
- Use bootstrap classes like
Syntax & Example: app.component.html
<div class="container-fluid mb-5">
<h1>Registration Form</h1>
<hr />
<form>
<!-- user name -->
<div class="form-group">
<label for="">Username:</label>
<input type="text" class="form-control">
</div>
<!-- password -->
<div class="form-group">
<label for="">Password:</label>
<input type="password" class="form-control">
</div>
<!-- confirm password -->
<div class="form-group">
<label for="">Confirm Password:</label>
<input type="password" class="form-control">
</div>
<!-- register button -->
<button class="btn btn-primary" type="submit">Register</button>
</form>
</div>
Image - Bootstrap Registration Form
To work with reactive/dynamic forms we need to import 'ReactiveFormsModule'
which provides bunch of classes/directives/utilities (FormGroup & FormControl)
necessary to build reactive/dynamic
- Define HTML
<form>
in component template/view/html file - Define component model in component class/.ts file
registrationForm = new FormGroup()
- Use directives provided by reactive forms module to associate the model with view
<form [formGroup]="registrationForm">
and<input formControlName="userName">, <input formControlName="password">
- We have already added html form
app.component.html
in last step - Now in
app.module.ts
:- import { ReactiveFormsModule } from '@angular/forms';
- also add to imports: [ ReactiveFormsModule ]
- import { ReactiveFormsModule } from '@angular/forms';
FormGroup
andFormControl
are two important building blocks classes for reactive/dynamic forms- In Reactive forms, the form is represented by
model in component class
, FormGroup and FormControl classes used to make that model FormGroup
represents whole/entire form (form
is instance ofFormGroup
class )FormControl
represents each form field (form fields
are instance ofFormControl
class )FormBuilder
handle form control creation, dynamic/run time field/FormControl creationValidators
helps to setup validation on each form control
- In Reactive forms, the form is represented by
Syntax & Example: app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { ReactiveFormsModule } from '@angular/forms';
@NgModule({
declarations: [
AppComponent,
],
imports: [
BrowserModule,
ReactiveFormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Syntax & Example: app.component.ts
import { Component } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
// create a formgroup instance
registrationForm = new FormGroup({
// details of objects/controls present in html
userName: new FormControl('Dinanath'), // defult value enter in bracket with quotes
password: new FormControl(''),
confirmPassword: new FormControl(''),
});
}
Syntax & Example: app.component.html
<div class="container-fluid mb-5">
<h1>Registration Form</h1>
<hr />
{{ registrationForm.value | json }}
<hr />
<!-- associate the model with view -->
<form [formGroup]="registrationForm">
<!-- user name -->
<div class="form-group">
<label for="">Username:</label>
<input formControlName="userName" type="text" class="form-control">
</div>
<!-- password -->
<div class="form-group">
<label for="">Password:</label>
<input formControlName="password" type="password" class="form-control">
</div>
<!-- confirm password -->
<div class="form-group">
<label for="">Confirm Password:</label>
<input formControlName="confirmPassword" type="password" class="form-control">
</div>
<!-- register button -->
<button class="btn btn-primary" type="submit">Register</button>
</form>
</div>
Image - Form Model, FormControl/FormGroup - defualt values
Image - Form Model, FormControl/FormGroup - updated values
- Inside the main form i.e. FormGroup, We can create a other FormGroup and store smaller object properties/FormControl
- create
'address'
a new FormGroup with 'street, landmark, road, postal-code' as a child FormControl - Under
'user details'
FormGroup create a username, gender, age, etc. as a child FormControl
- create
- In larger/complex forms such approach of creating Nested Form Groups will help to easily manage smaller chunks/sections
Syntax & Example: app.component.ts
// create a formgroup instance
registrationForm = new FormGroup({
// details of objects/controls present in html
userName: new FormControl('Dinanath'), // defult value enter in bracket with quotes
password: new FormControl(''),
confirmPassword: new FormControl(''),
// sub/nested formgroup
address: new FormGroup({
city: new FormControl('Mumbai'),
state: new FormControl('Maharashtra'),
postalcode: new FormControl(400001)
})
});
Syntax & Example: app.component.html
<div class="container-fluid mb-5">
<h1>Registration Form</h1>
<hr />
<span class="lead"><strong>Forms/FormGroup Values : registrationForm.value :</strong></span> {{ registrationForm.value | json }}
<hr />
<!-- associate the model with view -->
<form [formGroup]="registrationForm">
<!-- user name -->
<div class="form-group">
<label for="">Username:</label>
<input formControlName="userName" type="text" class="form-control">
</div>
<!-- password -->
<div class="form-group">
<label for="">Password:</label>
<input formControlName="password" type="password" class="form-control">
</div>
<!-- confirm password -->
<div class="form-group">
<label for="">Confirm Password:</label>
<input formControlName="confirmPassword" type="password" class="form-control">
</div>
<hr />
<h3>formGroupName="address: Nested FormGroup with child properties </h3>
<div formGroupName="address">
<div class="form-group">
<label for="">City:</label>
<input formControlName="city" type="text" class="form-control">
</div>
<div class="form-group">
<label for="">State:</label>
<input formControlName="state" type="text" class="form-control">
</div>
<div class="form-group">
<label for="">Postal Code:</label>
<input formControlName="postalcode" type="text" class="form-control">
</div>
</div>
<hr />
<!-- register button -->
<button class="btn btn-primary" type="submit">Register</button>
</form>
</div>
Image - Reactive Form - nested FormGroup
- Lets learn how to set FormControl values without any user interaction (set values programatically)
- We can retrieve back-end data by an API/service and set/update the values of FormControl with
'setValue()'
and'PatchValue()'
method setValue()
method works on FormGroup as well as FormControl class, But it accepts exact object structure which matches FormGroup with exact keys as FormControl, no custom deletion or addition of keys/properties allowed (will get an error). setValue is very strict with maintaining the structure of FormGroup - we must provide all FormControl values - we have to fill up/set the value of all the fieldspatchValue
method works on FormGroup as well as FormControl class, it accepts any fields - we can provide/pass value of any required field/few of the fields - we can fill up/set the value of only required fields- app.component.html: Create a button and kn app.component.ts: define methods:
<button class="btn btn-success ml-4" (click)="loadApiDataSetValue()">Load API Data <br/> SetValue() </button>
<button class="btn btn-secondary ml-4" (click)="loadApiDataPatchValue()">Load API Data <br> PatchValue() </button>
Syntax & Example: app.component.ts
import { Component } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
// create a formgroup instance
registrationForm = new FormGroup({
// details of objects/controls present in html
userName: new FormControl('Dinanath'), // defult value enter in bracket with quotes
password: new FormControl(''),
confirmPassword: new FormControl(''),
// sub/nested formgroup
address: new FormGroup({
city: new FormControl('Mumbai'),
state: new FormControl('Maharashtra'),
postalcode: new FormControl(400001)
})
});
loadApiDataSetValue() {
console.log('loadApiDataSetValue ');
The// setValue method works on FormGroup as well as FormControl class, But it accepts exact object structure which matches FormGroup with exact keys as FormControl, no custom deletion or addition of keys/properties allowed (will get an error). setValue is very strict with maintaining the structure of FormGroup - we must provide all FormControl values - we have to fill up/set the value of all the fields
this.registrationForm.setValue({
userName: 'Angular',
password: 'Angular6', //error - password field required to match FormGroup
confirmPassword: 'Angular6',
address: {
city: 'Google',
state: 'Google Corp',
postalcode: 12345,
}
})
}
loadApiDataPatchValue() {
console.log('loadApiDataPatchValue ');
// patch value method works on FormGroup as well as FormControl class, it accepts any fields - we can provide/pass the value of any required field/few of the fields - we can fill up/set the value of only required fields
this.registrationForm.patchValue({
// userName: 'React',
// password: 'React2',
// confirmPassword: 'React2',
address: {
city: 'Facebook',
state: 'Facebook Corp',
postalcode: 678901,
}
})
}
}
Syntax & Example: app.component.html
<div class="container-fluid mb-5">
<h1>Registration Form</h1>
<hr />
<span class="lead"><strong>Forms/FormGroup Values : registrationForm.value :</strong></span> {{ registrationForm.value | json }}
<hr />
<!-- associate the model with view -->
<form [formGroup]="registrationForm">
<!-- user name -->
<div class="form-group">
<label for="">Username:</label>
<input formControlName="userName" type="text" class="form-control">
</div>
<!-- password -->
<div class="form-group">
<label for="">Password:</label>
<input formControlName="password" type="password" class="form-control">
</div>
<!-- confirm password -->
<div class="form-group">
<label for="">Confirm Password:</label>
<input formControlName="confirmPassword" type="password" class="form-control">
</div>
<hr />
<h3>formGroupName="address: Nested FormGroup with child properties </h3>
<div formGroupName="address">
<div class="form-group">
<label for="">City:</label>
<input formControlName="city" type="text" class="form-control">
</div>
<div class="form-group">
<label for="">State:</label>
<input formControlName="state" type="text" class="form-control">
</div>
<div class="form-group">
<label for="">Postal Code:</label>
<input formControlName="postalcode" type="text" class="form-control">
</div>
</div>
<hr />
<!-- register button -->
<button class="btn btn-primary" type="submit">Register</button>
<button class="btn btn-success ml-4" (click)="loadApiDataSetValue()">Load API Data <br/> SetValue()
</button>
<button class="btn btn-secondary ml-4" (click)="loadApiDataPatchValue()">Load API Data <br/> PatchValue()
</button>
</form>
</div>
Image - Reactive Form - setValue() method
Image - Reactive Form - setValue() method - error
Image - Reactive Form - patchValue() method
- Creating multiple instances of FormControl classes (name/password/email/address fields) manually is pretty time consuming and repetitive
FormBuilder
service provides/consists of methods to handle/generate FormControls dynamically with lesser codeFormBuilder
is an alternate simpler service to create FormGroup and FormControls- Instead of FormGroup and FormControl import FormBuilder service and inject in the constructor
- Comment all old properties related to FormGroup and FormControl and create new with FormBuilder instance
Syntax & Example: app.component.ts
// import { FormGroup, FormControl } from '@angular/forms';
import { FormBuilder } from '@angular/forms';
constructor(private fb: FormBuilder) { }
/* // create a formgroup instance
registrationForm = new FormGroup({
// details of objects/controls present in html
userName: new FormControl('Dinanath'), // defult value enter in bracket with quotes
password: new FormControl(''),
confirmPassword: new FormControl(''),
// sub/nested formgroup
address: new FormGroup({
city: new FormControl('Mumbai'),
state: new FormControl('Maharashtra'),
postalcode: new FormControl(400001)
})
}); */
// create a FormBuilder instance
registrationForm = this.fb.group({
userName: ['Dinanath Test Name with Form Builder'],
password: [''],
confirmPassword: [''],
address: this.fb.group({
city: ['Mumbai'],
state: ['Maharashtra'],
postalcode: [400001]
})
})
Image - Reactive Form - FormBuilder
For common use cases (validation purpose) Reactive forms uses set of validators functions in class file only
, not in html/view/template
- Apply validation rules to form control - use
validators class
- import Validators Class -
import { FormBuilder, Validators } from '@angular/forms';
- add validators to form control item -
userName: ['Dinanath', Validators.required],
- import Validators Class -
- Provide a visual feedback for validations
- use class binding to show red border when form control/field is invalid -
<input [class.is-invalid]="registrationForm.get('userName').invalid && registrationForm.get('userName').touched" formControlName="userName" type="text" class="form-control">
- use class binding to show red border when form control/field is invalid -
- Display a appropriate error message for validation
<small class="text-danger" [class.d-none]="registrationForm.get('userName').valid || registrationForm.get('userName').untouched">* Name is required</small>
Syntax & Example: app.component.ts
import { FormBuilder, Validators } from '@angular/forms';
// create a FormBuilder instance
registrationForm = this.fb.group({
userName: ['Dinanath', Validators.required],
password: [''],
confirmPassword: [''],
address: this.fb.group({
city: ['Mumbai'],
state: ['Maharashtra'],
postalcode: [400001]
})
})
Syntax & Example: app.component.html
<!-- user name -->
<div class="form-group">
<label for="">Username:</label>
<!-- class binding to show red border when form control/field is invalid -->
<input [class.is-invalid]="registrationForm.get('userName').invalid && registrationForm.get('userName').touched" formControlName="userName" type="text" class="form-control">
<!-- single error with a class binding -->
<small class="text-danger" [class.d-none]="registrationForm.get('userName').valid || registrationForm.get('userName').untouched">* Name is required</small>
</div>
- To apply more than one validation rules, convert Validators to an array -
userName: ['Dinanath', [Validators.required, Validators.minLength(3)]],
- With
*ngIf
directive show conditioinal error messages
- Create
getter method
to return individual form control and use it in HTML file
Syntax & Example: app.component.ts
// getter for userName control/field to keep code short in html file
get userNameControl(){
return this.registrationForm.get('userName');
}
// create a FormBuilder instance
registrationForm = this.fb.group({
userName: ['Dinanath', [Validators.required, Validators.minLength(3)]],
password: [''],
confirmPassword: [''],
address: this.fb.group({
city: ['Mumbai'],
state: ['Maharashtra'],
postalcode: [400001]
})
})
Syntax & Example: app.component.html
<!-- user name -->
<div class="form-group">
<label for="">Username:</label>
<!-- class binding to show red border when form control/field is invalid -->
<!-- use getter method for userName control/field to keep code short in html file -->
<input [class.is-invalid]="userNameControl.invalid &&
userNameControl.touched" formControlName="userName" type="text" class="form-control">
<!-- single error with a class binding -->
<!-- <small class="text-danger" [class.d-none]="userNameControl.valid || userNameControl.untouched">* Name is required</small> -->
<!-- group or multiple error messages : error property -->
<div *ngIf="userNameControl.invalid && userNameControl.touched">
<small class="text-danger" *ngIf="userNameControl.errors?.required">* Name is required</small>
<small class="text-danger" *ngIf="userNameControl.errors?.minlength">* Name must be 3 characters</small>
</div>
</div>
Image - Reactive Form - Validators.required
Image - Reactive Form - Validators.required - show error text
Image - Reactive Form - Validators.minlength
Image - Reactive Form - Validators.minlength - show error text
- Some times
in-built validators
not match with the exact scenario/requirements - We can create custom validators for such scenarios like some keywords (spam words) not allowed in user name field, etc.
- Usually custom validators are used through-out the application so its advisable to create custom validators (function class) in an external class file and share
- create folder:
'app/shared/validators'
and inside this folder create a file named:'user-name.validator.ts'
- create a validator function
- Lets use/import newly created custom validator
'user-name.validator.ts'
inapp.component.ts
import { userNameValidator } from './shared/validators/user-name.validators';
userName: ['Dinanath', [Validators.required, Validators.minLength(3), userNameValidator]],
- Add an error message realted to custom validator in view
<small class="text-danger" *ngIf="userNameControl.errors?.validateUserNameError">* '{{userNameControl.errors?.validateUserNameError.value}}' user name not allowed</small>
Syntax & Example: user-name.validators.ts
import { AbstractControl } from "@angular/forms";
// create a validator function to avoid junk/spam names like `admin`
// it returns `string message` or null
export function userNameValidator(control: AbstractControl): { [key: string]: any } | null {
const isUserNameCorrect = /junk/.test(control.value);
return isUserNameCorrect ? { 'validateUserName': { value: control.value } } : null;
}
Syntax & Example: app.component.ts
import { userNameValidator } from './shared/validators/user-name.validators';
// create a FormBuilder instance
registrationForm = this.fb.group({
userName: ['Dinanath', [Validators.required, Validators.minLength(3), userNameValidator]],
password: [''],
confirmPassword: [''],
address: this.fb.group({
city: ['Mumbai'],
state: ['Maharashtra'],
postalcode: [400001]
})
})
Syntax & Example: app.component.html
<!-- group or multiple error messages : error property -->
<div *ngIf="userNameControl.invalid && userNameControl.touched">
<small class="text-danger" *ngIf="userNameControl.errors?.required">* Name is required</small>
<small class="text-danger" *ngIf="userNameControl.errors?.minlength">* Name must be 3 characters</small>
<small class="text-danger" *ngIf="userNameControl.errors?.validateUserNameError">* '{{userNameControl.errors?.validateUserNameError.value}}' user name not allowed</small>
</div>
Image - Reactive Form - custom validators
Image - Reactive Form - custom validators - error text
- Sometimes we need to validate values across multiple fields like 'password & confirm password', 'card number', 'verification of pin number' etc.
- Lets create a custom validator to match 'password & confirm password' fields
- In folder:
'app/shared/validators'
create a file named:'password.validator.ts'
- Apply custom password validator to formgroup-the main form (not to password formControl)
Syntax & Example: password.validator.ts
import { AbstractControl } from "@angular/forms";
// create a validator function to match password and confirm password field
// it returns `boolean` or null
export function passwordValidator(control: AbstractControl): { [key: string]: boolean } | null {
const passwordControl = control.get('password');
const confirmPasswordControl = control.get('confirmPassword');
if (passwordControl.pristine && confirmPasswordControl.pristine || passwordControl.untouched && confirmPasswordControl.untouched) {
return null;
}
return passwordControl && confirmPasswordControl && passwordControl.value != confirmPasswordControl.value ? { 'misMatchError': true } : null;
}
Syntax & Example: app.component.ts
import { passwordValidator } from './shared/validators/password.validator';
// create a FormBuilder instance
registrationForm = this.fb.group({
userName: ['Dinanath', [Validators.required, Validators.minLength(3), userNameValidator]],
password: [''],
confirmPassword: [''],
address: this.fb.group({
city: ['Mumbai'],
state: ['Maharashtra'],
postalcode: [400001]
})
}, {validator: passwordValidator})
Syntax & Example: app.component.html
<!-- confirm password -->
<div class="form-group">
<label for="">Confirm Password:</label>
<!-- class binding to show red border when form control/field is invalid -->
<input [class.is-invalid]="registrationForm.errors?.passwordMisMatchError" formControlName="confirmPassword" type="password" class="form-control">
<!-- single error with a class binding -->
<small class="text-danger" *ngIf="registrationForm.errors?.passwordMisMatchError">Confirm Password not matched!</small>
</div>
Image - Reactive Form - custom validators - cross field - error text
- Sometimes we need to apply dynamic validation like after certain action/thing/condition, validations should come in to the picture
- Example: After subscribe checkbox selection -> email field mendatory,
- Create
email text field
andsubscption checkbox field
in form - We need to track value of checkbox and conditionaly set status of email field
valueChanges
property helps to track the current value of any controls as a observablessetValidators
methods - set desired validators to formControl/fieldclearValidators
methods - clears validators from formControl/field
- Finally we need to invoke/call
updateValueAndValidity
method to reflect latest status
Syntax & Example: app.component.html
<!-- email -->
<div class="form-group">
<label for="">Email:</label>
<!-- class binding to show red border when form control/field is invalid -->
<input [class.is-invalid]="emailControl.invalid &&
emailControl.touched" formControlName="email" type="text" class="form-control">
<!-- single error with a class binding -->
<small class="text-danger" [class.d-none]="emailControl.valid || emailControl.untouched">* Email is required</small>
</div>
<!-- subscribe checkbox -->
<div class="form-check mb-3">
<input formControlName="subscribe" type="checkbox" class="form-check-input">
<label for="" class="form-check-label">Subscribe/Send me promotion offers</label>
</div>
Syntax & Example: app.component.ts
import { Component, OnInit } from '@angular/core';
// import { FormGroup, FormControl } from '@angular/forms';
import { FormBuilder, Validators, FormGroup } from '@angular/forms';
import { userNameValidator } from './shared/validators/user-name.validators';
import { passwordValidator } from './shared/validators/password.validator';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
registrationForm: FormGroup;
// getter for userName field to keep code short in html file
get userNameControl(){
return this.registrationForm.get('userName');
}
// getter for email control/field to keep code short in html file
get emailControl(){
return this.registrationForm.get('email');
}
constructor(private fb: FormBuilder) { }
ngOnInit() {
// create a FormBuilder instance
this.registrationForm = this.fb.group({
userName: ['Dinanath', [Validators.required, Validators.minLength(3), userNameValidator]],
email:[''],
subscribe:[false],
password: [''],
confirmPassword: [''],
address: this.fb.group({
city: ['Mumbai'],
state: ['Maharashtra'],
postalcode: [400001]
})
}, {validator: passwordValidator});
// subscribe checkbox
this.registrationForm.get('subscribe').valueChanges
.subscribe(subscribeCheckedValue => {
const email = this.registrationForm.get('email');
// email field set/unset `required` validators
if(subscribeCheckedValue){
email.setValidators(Validators.required);
} else {
email.clearValidators();
}
// to reflect latest correct status
email.updateValueAndValidity();
})
}
loadApiDataSetValue() {
console.log('loadApiDataSetValue ');
this.registrationForm.setValue({
userName: 'Angular',
password: 'Angular6',
confirmPassword: 'Angular6',
address: {
city: 'Google',
state: 'Google Corp',
postalcode: 12345,
}
})
}
loadApiDataPatchValue() {
console.log('loadApiDataPatchValue ');
this.registrationForm.patchValue({
// userName: 'React',
// password: 'React2',
// confirmPassword: 'React2',
address: {
city: 'Facebook',
state: 'Facebook Corp',
postalcode: 678901,
}
})
}
}
Image - Reactive Form - custom validators - conditional/hierarchy field - error text
Image - Reactive Form - custom validators - conditional/hierarchy field - success
- In some scenarios, we need to add fields/records on the fly like click on
Add Patient
button to add new patients record and so on, provide alternate email, contact details, etc. - The method of adding new fields at run-time keeps form concise and expand only when necessary
FormArray
class helps to maintain and duplicate dynamic list of controls- import { FormBuilder, Validators, FormGroup, FormArray } from '@angular/forms';
- Define
FormArray
in 'formModel'- alternateEmailAddresses:this.fb.array([])
- Create
getter method
to return individual form control and use/access easily it in HTML file - Create a method to dynamically insert controls to FormArray
- In view create a button
Add alternate Email
to invoke a method to add/push dynamic controls
Syntax & Example: app.component.ts
import { Component, OnInit } from '@angular/core';
// import { FormGroup, FormControl } from '@angular/forms';
import { FormBuilder, Validators, FormGroup, FormArray } from '@angular/forms';
import { userNameValidator } from './shared/validators/user-name.validators';
import { passwordValidator } from './shared/validators/password.validator';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
registrationForm: FormGroup;
// getter for userName field to keep code short in html file
get userNameControl(){
return this.registrationForm.get('userName');
}
// getter for email control/field to keep code short in html file
get emailControl(){
return this.registrationForm.get('email');
}
// getter for email control/field to keep code short in html file
get alternateEmailAddressesControl(){
return this.registrationForm.get('alternateEmailAddresses') as FormArray
}
addAlternateEmailAddresses() {
return this.alternateEmailAddressesControl.push(this.fb.control(''));
}
constructor(private fb: FormBuilder) { }
ngOnInit() {
// create a FormBuilder instance
this.registrationForm = this.fb.group({
userName: ['Dinanath', [Validators.required, Validators.minLength(3), userNameValidator]],
email:[''],
subscribe:[false],
password: [''],
confirmPassword: [''],
address: this.fb.group({
city: ['Mumbai'],
state: ['Maharashtra'],
postalcode: [400001]
}),
alternateEmailAddresses:this.fb.array([])
}, {validator: passwordValidator});
// subscribe checkbox
this.registrationForm.get('subscribe').valueChanges
.subscribe(subscribeCheckedValue => {
const email = this.registrationForm.get('email');
// email field set/unset `required` validators
if(subscribeCheckedValue){
email.setValidators(Validators.required);
} else {
email.clearValidators();
}
// to reflect latest correct status
email.updateValueAndValidity();
})
}
loadApiDataSetValue() {
console.log('loadApiDataSetValue ');
this.registrationForm.setValue({
userName: 'Angular',
password: 'Angular6',
confirmPassword: 'Angular6',
address: {
city: 'Google',
state: 'Google Corp',
postalcode: 12345,
}
})
}
loadApiDataPatchValue() {
console.log('loadApiDataPatchValue ');
this.registrationForm.patchValue({
// userName: 'React',
// password: 'React2',
// confirmPassword: 'React2',
address: {
city: 'Facebook',
state: 'Facebook Corp',
postalcode: 678901,
}
})
}
}
Syntax & Example: app.component.html
<!-- email -->
<div class="form-group">
<label for="">Email:</label>
<button type="button "class="btn btn-secondary btn-sm m-2" (click)="addAlternateEmailAddresses(); $event.preventDefault()">Add alternet Email</button>
<!-- class binding to show red border when form control/field is invalid -->
<input [class.is-invalid]="emailControl.invalid &&
emailControl.touched" formControlName="email" type="text" class="form-control">
<!-- single error with a class binding -->
<small class="text-danger" [class.d-none]="emailControl.valid || emailControl.untouched">* Email is required</small>
<!-- dynamic emaiil fields to add-->
<div formArrayName="alternateEmailAddresses" *ngFor="let email of alternateEmailAddressesControl.controls; let i=index">
<input type="text" class="form-control my-2" [formControlName]="i">
</div>
</div>
Image - Reactive Form - add dynamic email field/control
- Use
'novalidate'
attribute to form tag to avoid/prevent browserdefault validations
when will click on 'SUBMIT' button. - Bind
'ngSubmit'
event to the form tag which will trigger on 'SUBMIT' button<form [formGroup]="registrationForm" (ngSubmit)="onSubmit()">
- Define
onSubmit()
event handler inapp.component.ts
class file
Syntax & Example: app.component.html
<form [formGroup]="registrationForm" novalidate (ngSubmit)="onSubmit()">
Syntax & Example: app.component.ts
// handler for submit button
onSubmit() {
console.log('submit button clicked');
console.log(this.registrationForm.value);
}
- To send data to the server we need to create/use
'registration service'
with angular CLI by using the command:ng generate service registration
orng g s registration
registration.service.ts:
- Import HttpClient module: import { HttpClient } from '@angular/common/http';
- Invoke HttpClient in constructor as a local variable / Dependency injection:
constructor(public _httpClient:HttpClient) { }
app.module.ts:
- import HttpClientModule: import { HttpClientModule } from '@angular/common/http';
- add to the imports array:
imports: [ BrowserModule, FormsModule, HttpClientModule ],
registration.service.ts:
- // create a variable which hold path to which will post the date
_url = 'http://localhost:3000/enroll'; - // create a method called register which will post the data to server
register(userData) {
return this._httpClient.post
<any>
(this._url, userData); }
- // create a variable which hold path to which will post the date
app.component.ts:
-
The Post request will return response as an
observable
, so we need to subscribe to observables in app.component.ts -
Import registration service: import { RegistrationService } from './registration.service';
-
Invoke in constructor as a local variable / Dependency injection:
constructor(public registrationService:RegistrationService) { } -
On submit button clicked i.e. in onSubmit() method subscribe to the observables:
// handler for submit button onSubmit() { console.log('submit button clicked'); console.log(this.registrationForm.value);this.registrationService.register(this.registrationForm.value) .subscribe( response => console.log('Success', response), error => console.log('Error', error) ) }
-
Syntax & Example: app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';
import { ReactiveFormsModule } from '@angular/forms';
import { FormBuilderComponent } from './components/form-builder/form-builder.component';
@NgModule({
declarations: [
AppComponent,
FormBuilderComponent
],
imports: [
BrowserModule,
ReactiveFormsModule,
HttpClientModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Syntax & Example: registration.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
// to catch error
import { catchError } from 'rxjs/operators';
import { throwError } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class RegistrationService {
// create a variable which holds the path to which will post the date
_url = 'http://localhost:3000/enroll';
constructor(public _httpClient: HttpClient ) { }
// create a method called enroll which will post the data to server
register(userData) {
return this._httpClient.post<any>(this._url, userData)
.pipe(catchError(this.errorHandler)) //catch errors
}
errorHandler(error: HttpErrorResponse) {
return throwError(error);
}
}
Syntax & Example: app.component.ts
import { Component, OnInit } from '@angular/core';
// import { FormGroup, FormControl } from '@angular/forms';
import { FormBuilder, Validators, FormGroup, FormArray } from '@angular/forms';
import { userNameValidator } from './shared/validators/user-name.validators';
import { passwordValidator } from './shared/validators/password.validator';
import { RegistrationService } from './registration.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
registrationForm: FormGroup;
// getter for userName field to keep code short in html file
get userNameControl() {
return this.registrationForm.get('userName');
}
// getter for email control/field to keep code short in html file
get emailControl() {
return this.registrationForm.get('email');
}
// getter for email control/field to keep code short in html file
get alternateEmailAddressesControl() {
return this.registrationForm.get('alternateEmailAddresses') as FormArray
}
addAlternateEmailAddresses() {
return this.alternateEmailAddressesControl.push(this.fb.control(''));
}
// create a new data member/property to bind to the view
errorMessage = '';
// to check form submitted or not
isFormSubmitted = false;
constructor(private fb: FormBuilder, public registrationService: RegistrationService) { }
ngOnInit() {
// create a FormBuilder instance
this.registrationForm = this.fb.group({
userName: ['Dinanath', [Validators.required, Validators.minLength(3), userNameValidator]],
email: [''],
subscribe: [false],
password: [''],
confirmPassword: [''],
address: this.fb.group({
city: ['Mumbai'],
state: ['Maharashtra'],
postalcode: [400001]
}),
alternateEmailAddresses: this.fb.array([])
}, { validator: passwordValidator });
// subscribe checkbox
this.registrationForm.get('subscribe').valueChanges
.subscribe(subscribeCheckedValue => {
const email = this.registrationForm.get('email');
// email field set/unset `required` validators
if (subscribeCheckedValue) {
email.setValidators(Validators.required);
} else {
email.clearValidators();
}
// to reflect latest correct status
email.updateValueAndValidity();
})
}
loadApiDataSetValue() {
console.log('loadApiDataSetValue ');
this.registrationForm.setValue({
userName: 'Angular',
password: 'Angular6',
confirmPassword: 'Angular6',
address: {
city: 'Google',
state: 'Google Corp',
postalcode: 12345,
}
})
}
loadApiDataPatchValue() {
console.log('loadApiDataPatchValue ');
this.registrationForm.patchValue({
// userName: 'React',
// password: 'React2',
// confirmPassword: 'React2',
address: {
city: 'Facebook',
state: 'Facebook Corp',
postalcode: 678901,
}
})
}
// handler for submit button
onSubmit() {
console.log('submit button clicked');
console.log(this.registrationForm.value);
this. isFormSubmitted = true;
this.registrationService.register(this.registrationForm.value)
.subscribe(
response => console.log('Success', response),
// error => console.log('Error', error)
// store error in data member / property to bind to the view
error => this.errorMessage = error.statusText
)
}
}
Image - Reactive Form - Submit Form data
- At the root, besides the angular application folder create a new siblings folder named
'server'
which consists of server-side code - Run the command:
npm init --yes
to create apackage.json
also bypass-surpass the questions with default answers (without giving answers to questions) - Install express and other dependencies with the command:
npm install --save express body-parser cors
"dependencies": {
"body-parser": "^1.18.3", - middleware to handle form data
"cors": "^2.8.4", - helps to make request through multiple ports/servers - cross origin resource sharing
"express": "^4.16.3" - web server
}
- Inside a
server
folder create a new file named'server.js'
- at command prompt/terminal run command:
node server
- will get output in terminal as 'Server running on localhost port: 3000' - in browser type path:
'http://localhost:3000/'
- output - 'Hello from the server!' - add an endpoint in server.js to which angular application will post data // add endpoint app.post('/enroll', function(req,res){ // req.body - contains user data send by the angular console.log(req.body); // send response res.status(200).send({'message': 'Data received'}); })
- insert/add endpoint path to angular url variable in 'enrollment.service.ts' _url = 'http://localhost:3000/enroll';
- restart the node server by command:
node server
- In angular application click on the submit button and check
inspect element
console as well as node console will get the message and user data as an output. for better usability its advisable to hide actual form/hide submit button / disable submit button etc. to avoid the extra click on submit button.
Syntax & Example: server.ts
// 1. imports/requires the packages
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
// port
const PORT = 3000;
const app = express();
// handle the json data
app.use(bodyParser.json());
app.use(cors());
// test/check get request
app.get('/',function(req, res){
res.send('Hello from server!');
})
// add endpoint
app.post('/enroll', function(req,res){
// req.body - contains user data send by the angular
console.log(req.body);
// send response
res.status(200).send({'message': 'Data received'});
// to see errors
// res.status(401).send({'message': 'Data received'});
})
// listen to request
app.listen(PORT, function(){
console.log('Server running on localhost port: ', PORT);
})
Image - Reactive Form - Submit Form data with Node Server
Image - Reactive Form - Submit Form data with Node Server Response