- 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
- Forms are the main, critical building block of any modern front-end Web/Mobile application
The real objective of forms is gathering data
- While dealing with Forms one should create an efficient and effective workflow, User Experience (UX) and User Interface (UI) which guide users properly
- In the context of Form, the Angular framework provides support for
two-way data binding, change tracking, validation, and error handling
- Authentication
- Register / Login / Profile creation
- Submit request / Place an Order
- Pay Bills
- Schedule appointments
- Also to perform many other countless data entry operations/tasks
- Data binding
- Change tracking
- Validation
- Visual feedback
- Error messages
- Form submission
Basic familiarity with HTML, CSS, and JavaScript, Angular 2/4/5/6 is a must.
- HTML - Markup
- CSS - Style, Formates
- JavaScript - Behaviour, Click, Validations
- Angular - Templates, Components, Data Binding, Modules, Services, etc.
- TypeScript basics - Advanced JS features (Class, Arrow Function, Spread Operator)
- Text Editor / Visual Text Editors
In this section, we will learn how to set up a local development environment to start developing Angular apps.
Node
, (website: https://nodejs.org/en)NPM
, (Node Package Manager - comes inbuilt with Node)Angular CLI = Command Line Interface
, Angular CLI (Command Line Interface) for angular (website: https://cli.angular.io/),- The quickest and easiest way of starting an Angular app is through the
Angular CLI (Command Line Interface)
. It allows/helps the developer to build/generate building blocks of angular application like component, services, routings, modules, etc. with best practices quicker and easier)
- The quickest and easiest way of starting an Angular app is through the
Text Editor
- Visual Studio Code / Visual Studio Code Insiders (website: https://code.visualstudio.com)
- Sublime Text,
- Atom,
- Brackets etc.
Component Template Markup (.HTML view - Collects data)
-> Class (.TS - Binds Data)
-> Services (.TS Send Data)
-> Server
- Template Driven Forms:
- Primarily depends on the component template (manual HTML)
- Most of the code written in the component (.HTML) template file
- Template-driven forms are useful for adding a simple form to an app, such as an email list, signup form, etc.
- Reactive Model Driven Forms:
- Primarily depends on the component class (dynamic)
- Majority of code written in component .ts class file
- Reactive forms are more robust, scalable, reusable, and testable
In Template Driven forms, most of the logic/code resides in the .HTML/view/template file. The template is responsible for setting up the form, the validation, control, group etc.
- Template Driven Forms (TDFs) are easy to use and similar to AngularJs (Angular 1.x) forms
- TDFs are mainly/heavily relay and depends on
Two Way Data Binding
- The
ngModel
directives take care of actual input field values and data changes - TDFs most of the code is a manual template, so it consists of bulky (.HTML) code/markups and minimal component code
ngForm
directive along withngModel
directive automatically tracks the form and form elements state & validity
Unit testing
is the biggest challenge (we need to runEnd2End test (e2e)
with browser)- Due to heavy markup in the template,
readability decreases and markup code looks complex
- TDFs are
suitable for simple scenarios
only, Reactive forms are used for complex validations, unit test validations
- Create/generate new Angular CLI project
- Add a form HTML/markup
- Binding the Data (
ngForm
andngModel
directives) - Tracking state and validity (Add validation using
built-in validators
) - Providing visual Feedback (Display meaningful messages-feedbacks)
- Displaying error messages (Display validation errors)
- Posting data to server/
express server
(Handle form submission usingngSubmit
)
- 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-templatedriven
(after creation check the newly generated folder structure)
Image - Angular project/app folder structure
- To run/serve the application, at command prompt type command:
ng serve
orng serve --port 5000
(change the port number) - 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>TemplateDrivenForms</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 enrollment form- Use bootstrap classes like
form-group
,form-control
,form-check
andform-check-input
, etc. with div and input field respectively to create a form fields with standard look and feel - Create a name and email
input
fields - Create a
Drop-Down
menu with<select>
tag and add/show drop down menu options with*ngFor
structural directive - Create a
radio
button group with<input type="radio">
to show/select Time preference - Create a
checkbox
with<input type="checkbox">
to check/opt for promotional offer
- Use bootstrap classes like
- In
app.component.ts
class file- Create a new property
topics
to store an array of topics and to display as a select drop-down menu options
- Create a new property
Syntax & Example: app.component.html
<div class="container-fluid">
<h1>Enrollment Form</h1>
<hr />
<form>
<!-- name -->
<div class="form-group">
<label for="">Name</label>
<input type="text" class="form-control">
</div>
<!-- email -->
<div class="form-group">
<label for="">Email</label>
<input type="email" class="form-control">
</div>
<!-- phone -->
<div class="form-group">
<label for="">Phone</label>
<input type="tel" class="form-control">
</div>
<!-- user can select the topics bind with array -->
<div class="form-group">
<select class="custom-select">
<option selected>Choose Your Interested Topic</option>
<option *ngFor="let topic of topics">{{ topic }}</option>
</select>
</div>
<!-- radio button group -->
<div class="mb-3">
<label for="">Time Preference</label>
<div class="form-check">
<input type="radio" class="form-check-input" name="timePreference" value="morning">
<label for="" class="form-check-label">Morning</label>
</div>
<div class="form-check">
<input type="radio" class="form-check-input" name="timePreference" value="evening">
<label for="" class="form-check-label">Evening</label>
</div>
</div>
<!-- checkbox -->
<div class="form-check mb-3">
<input type="checkbox" class="form-check-input">
<label for="" class="form-check-label">Send me promotional offers</label>
</div>
<!-- submit button -->
<div>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
</div>
Syntax & Example: app.component.ts
/* topics array */
topics = ['JavaScript', 'Angular', 'React', 'Vue'];
Image - Bootstrap Registration Form
Image - Bootstrap Registration Form with Drop Down Menu *ngFor
-
FormsModule: Angluar template-driven forms are in their own module named
FormsModule
, to use the template driven form, we need toexplicitly import {FormsModule}
in our application module from@angular/forms
-
ngForm: The
ngForm is an instance of the FormGroup
. The FormGroup represents the group ofFormControl
from HTML form tag, each form is a FormGroup because it will have at least one FormControl that gives us access to(ngSubmit)
which can be bind to a method in our component and triggered when we submit the form.The NgForm directive supplements the form element with additional features
. -
ngModel: We need the
ngModel in the form input, and the input must be named too
.[(ngModel)]
follows[(banana in the box)]
syntax and performstwo-way binding
for reading and writing input control values. If a[(ngModel)]
directive is used, the input field takes an initial value from the bound component class and updates it back whenever any change to the input control value is detected(on keystroke and button press)
. -
ngModelGroup:
ngModelGroup
is useful and helps to create group and sub-groups within forms likengModelGroup="address" -> and its sub child's like street, state, postal code, etc., ngModelGroup="person" -> and its sub child's name, age, gender, nationality etc.,
and so on
- In
app.module.ts
module class file- import angular
FormsModule
and also include inimports array
- import angular
- In file
app.component.html
the main markup file- As and When we use
form tag, angular attaches its inbuilt ngForm
directive to give/pass and manage valuable information of forms ngForm
directive denotes values of different fields/form-control and also values are valid or invalid
- As and When we use
- Template Reference Variable (TRF) is used to get the complete reference of the ngForm directive, we can use:
<form #userForm="ngForm">
- Use interpolation to see/get form-control values entered:
{{ userForm.value | json }}
- By default angular does not track each and every form-controls, to track repsective field use
ngModel
directive with respective form field - Error will generate:
ERROR Error: If ngModel is used within a form tag, either the name attribute must be set or the form control must be defined as 'standalone' in ngModelOptions
- To resolve error we must need to use
name
attribute with every input form-control withngModel
:<input type="text" class="form-control" name="userName" ngModel>
- Check the difference in
user form.value
object - Enter data in every form field and again verify
userForm.value
- Check the difference in
- Use interpolation to see/get form-control values entered:
- We can collect and
send data to the server by using userForm.value
but the better approach is to bind data touser defined model
and send model data to the server
Syntax & Example: app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
imports: [
BrowserModule,
FormsModule
]
Syntax & Example: app.component.html
<div class="container-fluid">
<h1>Enrollment Form</h1>
<hr />
<form #userForm="ngForm">
<span class="lead"><strong>Forms Values : userForm.value :</strong></span> {{ userForm.value | json }}
<hr />
<!-- name -->
<div class="form-group">
<label for="">Name</label>
<input type="text" class="form-control" name="userName" ngModel>
</div>
<!-- email -->
<div class="form-group">
<label for="">Email</label>
<input type="email" class="form-control" name="email" ngModel>
</div>
<!-- phone -->
<div class="form-group">
<label for="">Phone</label>
<input type="tel" class="form-control" name="phone" ngModel>
</div>
<!-- user can select the topics bind with array -->
<div class="form-group">
<select class="custom-select" name="topic" ngModel>
<option selected>Choose Your Interested Topic</option>
<option *ngFor="let topic of topics">{{ topic }}</option>
</select>
</div>
<!-- radio button group -->
<div class="mb-3">
<label for="">Time Preference</label>
<div class="form-check">
<input type="radio" class="form-check-input" name="timePreference" value="morning" ngModel>
<label for="" class="form-check-label">Morning</label>
</div>
<div class="form-check">
<input type="radio" class="form-check-input" name="timePreference" value="evening" ngModel>
<label for="" class="form-check-label">Evening</label>
</div>
</div>
<!-- checkbox -->
<div class="form-check mb-3">
<input type="checkbox" class="form-check-input" name="subscribe" ngModel>
<label for="" class="form-check-label">Send me promotional offers</label>
</div>
<!-- submit button -->
<div>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
</div>
Image - Template Driven Form - ngModel error
Image - Form Model, FormControl/FormGroup - defualt values
Image - Form Model, FormControl/FormGroup - updated values
- Here will try to create
user defined model
, as soon as user will enter data will update an instance of user model and send model data to the server - Inside the app/project folder/directory generate a new class which acts as a model with the command:
ng generate class User
orng g class User
- It will generate a new file named:
user.ts
, type in the differentproperties
of user class likename, email, phone - as per fields used in the form
- User model is ready with us now create an instance of model in
app.component.ts
with new property nameduserModel
which consists of user default details/information:userModel = new User('Dinanath', 'dinanathj@gmail.com', 9892221165, 'default', 'morning', true);
- Custom userModel instance with data is ready in the class file now its time to
bind userModel with enrollment form using interpolation {{ userModel | JSON }}
- To bind the userModel properties with form field use property binding denotes by a square bracket to the ngModel directive and userModel properties
[(ngModel)]="userModel.name"
and so on... Property data binding by square bracket only is one-way data binding [(ngModel]
-> data flow from class to the view but not flows from view to the class- So to implement/achieve two-way
data binding use banana in the box ie [(ngModel)]
, two-way data binding sync model and view
Syntax & Example: user.ts
export class User {
constructor(
public name: string,
public email: string,
public phone: number,
public topic: string,
public timePreference: string,
public subscribe: boolean
){}
}
Syntax & Example: app.component.ts
import { User } from './user';
userModel = new User('Dinanath', 'dinanathj@gmail.com', 9892221165, 'default', 'morning', true);
Syntax & Example: app.component.html
<div class="container-fluid">
<h1>Enrollment Form</h1>
<hr />
<form>
<span class="lead"><strong>Forms Values : userForm.value :</strong></span> {{ userForm.value | json }}
<hr />
<span class="lead"><strong>Forms Values : userModel data :</strong></span> {{ userModel | json }}
<hr />
<!-- name -->
<div class="form-group">
<label for="">Name</label>
<!-- two way data binding used to update view and class both -->
<input type="text" class="form-control" name="userName" [(ngModel)]="userModel.name">
</div>
<!-- email -->
<div class="form-group">
<label for="">Email</label>
<input type="email" class="form-control" name="email" [(ngModel)]="userModel.email">
</div>
<!-- phone -->
<div class="form-group">
<label for="">Phone</label>
<input type="tel" class="form-control" name="phone" [(ngModel)]="userModel.phone">
</div>
<!-- user can select the topics bind with array -->
<div class="form-group">
<select class="custom-select" name="topic" [(ngModel)]="userModel.topic">
<option value="default">Choose Your Interested Topic</option>
<option *ngFor="let topic of topics">{{ topic }}</option>
</select>
</div>
<!-- radio button group -->
<div class="mb-3">
<label for="">Time Preference</label>
<div class="form-check">
<input type="radio" class="form-check-input" name="timePreference" value="morning" [(ngModel)]="userModel.timePreference">
<label for="" class="form-check-label">Morning</label>
</div>
<div class="form-check">
<input type="radio" class="form-check-input" name="timePreference" value="evening" [(ngModel)]="userModel.timePreference">
<label for="" class="form-check-label">Evening</label>
</div>
</div>
<!-- checkbox -->
<div class="form-check mb-3">
<input type="checkbox" class="form-check-input" name="subscribe" [(ngModel)]=userModel.subscribe>
<label for="" class="form-check-label">Send me promotional offers</label>
</div>
<!-- submit button -->
<div>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
</div>
Image - User Class - works as Data Model
Image - User Class - formvalue, modelvalue
Image - User Class, form model, Two Way Data Binding
- As Validations are an important aspect of programming, before sending data to the server it's pretty important to
validate the form data and show user respective visual feedback or error message
- Using
ngModel
in a form gives you more than just two-way data binding:- It also tells you if the user touched the control (state & activity),
- If the value changed, or
- If the value became valid/invalid (validity)
- The
NgModel
directive doesn't just track state:- It updates the control with special
Angular CSS classes that reflect the state
, - We can leverage/utilize those class names to change the appearance of the control
- It updates the control with special
Angular automatically attach classes like ng-invalid, ng-dirty, ng-touched
to input fields
STATE | CLASS applied (if TRUE) |
CLASS applied (if FALSE) |
---|---|---|
The control has been visited/clicked/touched ( class will apply/change only on blur event , user need to navigate away from form control) |
ng-touched | ng-untouched |
The controls value has been changed (class will apply when user type something) |
ng-dirty | ng-pristine |
The controls value is valid | ng-valid | ng-invalid |
- Add a
#Template Reference Variable (#TRV)
named#name
to theName <input> field
and use it to display the input's CSS classes- Use interpolation to see/get form-control classes:
{{ name.className }}
- Right click on element and check in browser
inspect element
panel the respective angular state classes applied
- Use interpolation to see/get form-control classes:
Syntax & Example: app.component.html (to apply state and class)
<!-- name -->
<div class="form-group">
<label for="">Name</label>
<!-- two way data binding used to update view and class both -->
<input type="text" #name required class="form-control" name="userName" [(ngModel)]="userModel.name">
</div>
<span class="text-primary"><strong>Angular Classes : name.className :</strong></span> {{ name.className }}
- With each of the
state classes
angular also provide an alternative associatedstate properties
on the ngModel directive- properties names are the same as the class name, only
ng-
removed from it
- properties names are the same as the class name, only
CLASS | PROPERTY |
---|---|
.ng-untouched | untouched |
.ng-touched | touched |
.ng-pristine | pristine |
.ng-dirty | dirty |
.ng-valid | valid |
.ng-invalid | invalid |
#name
reference variable by default point to input element/field of DOM, by assigning#name="ngModel"
willpoint to ngModel
so all ngModel properties will be available to us- Use interpolation to see/get form-control properties:
{{ name.touched }} {{ name.pristine }} {{ name.valid }}
- Right click on element and check in browser
inspect element
panel the respective angular state classes applied with respective properties
- Use interpolation to see/get form-control properties:
Syntax & Example: app.component.html (to apply state and properties)
<!-- name -->
<div class="form-group">
<label for="">Name</label>
<!-- two way data binding used to update view and class both -->
<input type="text" #name="ngModel" required class="form-control" name="userName" [(ngModel)]="userModel.name">
</div>
<span class="text-success"><strong>Angular ngModel properties used for input fields:</strong> touched - {{ name.touched }} <span class="text-warning">|</span> pristine - {{ name.pristine }} <span class="text-warning">|</span> valid - {{ name.valid }}</span>
Image - Form - State class - default
Image - Form - State class - changed/upated/modified
Image - Form - State Properties - default
Image - Form - State Properties - changed/upated/modified
- A good user experience is to visually indicate to the user
form field is valid/invalid
while feeling up the form/entering the details - Usually one can use attributes/properties like
Required, Maxlength, Minlength and Pattern
for form validation on form input fields - We can use Angular inbuilt validation classes or can create a custom css class and use for indication
Note: bootstrap uses a class
'is-invalid'
to show a red border around input fields
Syntax & Example: styles.css
/* create custom css class for indicating invalid visual border */
.form-control.ng-invalid.ng-touched {
border: 1px solid coral;
box-shadow: 1px 1px 5px 1px coral;
}
- Check if the name is
invalid
and alsotouched
(something entered and removed) then only apply a bootstrap/custom class named'is-invalid'
but don't apply initially without touch: - Apply pattern matching validation input only
10 digits mobile number - pattern="^\d{10}$"
Syntax & Example: app.component.html (apply validation class to show red border)
<!-- name -->
<div class="form-group">
<label for="">Name</label>
<!-- two way data binding used to update view and class both -->
<input type="text" #name="ngModel" required [class.is-invalid]="name.invalid && name.touched" class="form-control" name="userName" [(ngModel)]="userModel.name" placeholder="Name required*">
</div>
<!-- phone -->
<div class="form-group">
<label for="">Phone</label>
<input type="tel" #phone="ngModel" required pattern="^\d{10}$" [class.is-invalid]="phone.invalid && phone.touched"class="form-control" name="phone" [(ngModel)]="userModel.phone" placeholder="10 digits mobile number">
</div>
Image - Form Default properties/class
Image - Form field - show Red border
- Its essential/advisable to show user visual red border with appropriate Error Messages/feedback
Note: Bootstrap class
'd-none'
is used tohide/display:none
for any element. We can use either'*ngIf'
or'class binding'
or both to display error messages.
Syntax & Example: app.component.html
<!-- name -->
<div class="form-group">
<label for="">Name</label>
<!-- two way data binding used to update view and class both -->
<input type="text" #name="ngModel" required [class.is-invalid]="name.invalid && name.touched" class="form-control" name="userName" [(ngModel)]="userModel.name" placeholder="Name required*">
<!-- single error with a class binding -->
<small class="text-danger" [class.d-none]="name.valid || name.untouched">* Name is required</small>
</div>
<!-- phone -->
<div class="form-group">
<label for="">Phone</label>
<input type="tel" #phone="ngModel" required pattern="^\d{10}$" [class.is-invalid]="phone.invalid && phone.touched"class="form-control" name="phone" [(ngModel)]="userModel.phone" placeholder="10 digits mobile number">
<!-- single error with a class binding -->
<small class="text-danger" [class.d-none]="phone.valid || phone.untouched">Enter 10 digits mobile number</small>
</div>
- Some times we come across a scenario where we need to apply multiple validations and also we need to show a different error message
Syntax & Example: app.component.html
<!-- phone -->
<div class="form-group">
<label for="">Phone</label>
<input type="tel" #phone="ngModel" required pattern="^\d{10}$" [class.is-invalid]="phone.invalid && phone.touched"class="form-control" name="phone" [(ngModel)]="userModel.phone" placeholder="10 digits mobile number">
<!-- single error with a class binding -->
<!-- <small class="text-danger" [class.d-none]="phone.valid || phone.untouched">Enter 10 digits mobile number</small> -->
<!-- group or multiple error messages : error property -->
<div *ngIf="phone.errors && (phone.invalid || phone.touched)">
<small class="text-danger" *ngIf="phone.errors.required">* Phone is required</small>
<small class="text-danger" *ngIf="phone.errors.pattern">* Phone number must be 10 digits</small>
</div>
</div>
Image - Class binding error messages
Image - Error property showing multiple/different errors
Image - Error property showing multiple/different errors
Select option drop down menu
validation with required and showing error message: we need to listen/check 'Blur'/'Change'
event and verify option selected and accordingly set validation/show error messages:
Syntax & Example: app.component.html
<!-- user can select the topics bind with array -->
<div class="form-group">
<select (blur)="validateChoosenTopic(topic.value)" (change)="validateChoosenTopic(topic.value)" #topic="ngModel" [class.is-invalid]="topicHasError && topic.touched" class="custom-select" name="topic" [(ngModel)]="userModel.topic">
<option value="default">Choose Your Interested Topic</option>
<option *ngFor="let topic of topics">{{ topic }}</option>
</select>
<small class="text-danger" [class.d-none]="!topicHasError ||topic.untouched">Please choose right topic</small>
</div>
Syntax & Example: app.component.ts
// class member to hold select option error state
topicHasError = true;
// select option validation
validateChoosenTopic(curValue) {
if (curValue === 'default') {
this.topicHasError = true;
} else {
this.topicHasError = false;
}
}
Image - Topic Select Dropdown validation
- Whenever we add the
<form>
tag/element in to the html page, angular automatically attaches the'ngForm'
directive to the form tag - We can get the reference of the
'ngForm'
directive by using#Template Reference Variable (#TRV)
, example:<form #userForm="ngForm">
- Form validation properties can be used for global level operations like
enable/disable of 'SUBMIT'
button etc. - Use interpolation to see/get form-validation status:
{{ userForm.form.valid }}
Syntax & Example: app.component.html
<form #userForm="ngForm">
<span class="lead"><strong>Form Validation status : userForm.form.valid :</strong></span> {{ userForm.form.valid }}
<!-- submit button -->
<div>
<!-- form level validation to enable/disable button -->
<button [disabled]="userForm.form.invalid || topicHasError" type="submit" class="btn btn-primary">Submit</button>
</div>
Image - userForm.form.valid status - Success
Image - userForm.form.valid status - false/invalid
- Use
'novalidate'
attribute to form tag to avoid/prevent browserdefault validations
when will click on 'SUBMIT' button<form #userForm="ngForm" novalidate>
- Bind
'ngSubmit'
event to the form tag which will trigger on 'SUBMIT' button<form #userForm="ngForm" novalidate (ngSubmit)="onSubmit()">
- Define
onSubmit()
event handler inapp.component.ts
class file
Syntax & Example: app.component.html
<form #userForm="ngForm" novalidate (ngSubmit)="onSubmit()">
Syntax & Example: app.component.ts
// handler for submit button
onSubmit() {
console.log('submit button clicked');
console.log(this.userModel);
}
- To send data to the server we need to create/use
'enrolment service'
with angular CLI by using the command:ng generate service enrollment
orng g s enrollment
enrollment.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 ],
enrollment.service.ts:
- // create a variable which hold path to which will post the date
_url = 'http://localhost:3000/enroll'; - // create a method called enroll which will post the data to server
enroll(user:User){ return this._httpClient.post<any>
(this._url,user); }
- // 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 enrollment service: import { EnrollmentService } from './enrollment.service';
- Invoke in constructor as a local variable / Dependency injection:
constructor(public enrollmentService:EnrollmentService) { } - On submit button clicked i.e. in onSubmit() method subscribe to the observables:
- The Post request will return response as an
Syntax & Example: app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent,
],
imports: [
BrowserModule,
FormsModule,
HttpClientModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Syntax & Example: enrollment.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { User } from './user';
@Injectable({
providedIn: 'root'
})
export class EnrollmentService {
// 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 the server
enroll(user: User) {
return this._httpClient.post<any>(this._url, user)
}
}
Syntax & Example: app.component.ts
// handler for submit button
onSubmit() {
console.log('submit button clicked');
console.log(this.userModel);
this.enrollmentService.enroll(this.userModel)
.subscribe(
data => console.log('Success!', data),
error => console.error('Error!', error)
)
}
Image - Submit - Check userModel value
- 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
- express: node web server
- body-parser: middleware to read/send/pass form data (to handle form data)
- cors: cross origin port/domain request (helps to make request through multiple ports/servers - cross origin resource sharing)
Syntax & Example: package.json
"dependencies": {
"body-parser": "^1.18.3",
"cors": "^2.8.4",
"express": "^4.16.3"
}
- Inside a
server
folder create a new file named'server.js'
and required logic - 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 server!'
Image - Node Server Running
- add 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 - Template Driven Form - Submit Form data
Image - Template Driven Form - Submit Form data with Node Server Response
- To catch error we need support of
'rxjs'
operators
Syntax & Example: enrollment.service.ts
// to catch error
import { catchError } from 'rxjs/operators';
import { throwError } from 'rxjs';
// create a method called enroll which will post the data to the server
enroll(user:User){
return this.httpClient.post`<any>`(this._url,user)
.pipe(catchError(this.errorHandler)) //catch errors
}
errorHandler(error: HttpErrorResponse) {
return throwError(error);
}
- By defualt we log error to console its advisable to create a new data member/property named 'errorMessage' and bind to the view.
Syntax & Example: app.component.ts:
// store error in data member / property to bind to the view
error => this.errorMessage = error.statusText
-
Syntax & Example: app.component.html
<!-- errorMessage bind to the view -->
<div class="alert alert-danger" *ngIf="errorMessage">
{{ errorMessage }}
</div>
- in server.js change: res.status(200) to res.status(401).send({'message': 'Data received'});
- restart node server, load angular application application, click on submit and check
'ngForm'
directive gives us entire information (different properties/methods/objects/values used with form/form fields)- To examine/understand just pass current form ie.
#userForm
parameter in submit button event handler function and check ininspect element/developer tools
-> console panel: NgForm
object -> form:FormGroup
->controls
-> each and every form field is of type FormControlFormGroup
andFormControl
are building blocks of reactive forms- Template Driven Form approach is behind the scene works as Model Driven approach where models are automatically created by angular
Syntax & Example: app.component.html:
<form #userForm="ngForm" novalidate (ngSubmit)="onSubmit(userForm)">
Syntax & Example: app.component.ts:
onSubmit(curForm) {
console.log('current User Form reactive details: ', curForm);
}
Image - Template Driven Form vs Module Driven/Reactive Form