Templatedriven Forms
Templatedriven Forms
Before we dive into the actual component template, we need to have an abstract idea of what we are
building. So here is the form structure that I have in my mind. The signup form will have several input
fields, a select element, and a checkbox element.
Here is the HTML template that we will be using for our registration page.
HTML Template
4 <form class="form-horizontal">
5 <fieldset>
6 <legend>SignUp</legend>
9 <div class="form-group">
10 <label for="inputEmail">Email</label>
11 <input type="text"
12 id="inputEmail"
13 placeholder="Email">
14 </div>
16 <div class="form-group">
17 <label for="inputPassword">Password</label>
18 <input type="password"
19 id="inputPassword"
20 placeholder="Password">
21 </div>
22
23 <div class="form-group">
25 <input type="password"
26 id="confirmPassword"
27 placeholder="Password">
28 </div>
29
31 <div class="form-group">
32 <label for="select">Gender</label>
33 <select id="select">
34 <option>Male</option>
35 <option>Female</option>
36 <option>Other</option>
37 </select>
38 </div>
39
42 <label>
44 Conditions
45 </label>
46 </div>
47
49 <div class="form-group">
52 </div>
53 </fieldset>
54 </form>
55 </div>
56 </div>
The CSS classes used in the HTML template are part of the Bootstrap library used for making things
pretty. Since this is a not a design tutorial, I won't be talking much about the CSS aspects of the form
unless necessary.
Advertisement
To use the template-driven form directives, we need to import the FormsModule from @angular/forms
and add it to the import array in app.module.ts.
app/app.module.ts
2 @NgModule({
3 .
4 .
5 imports: [
6 BrowserModule,
7 FormsModule
8 ],
9 .
10 .
11 })
Next, create a class that will hold all properties of the User entity. We can either use an interface and
implement it in the component or use a TypeScript class for the model.
app/User.ts
2 constructor(
9 ){ }
10
11 }
app/signup-form/signup-form.component.ts
4 @Component({
5 selector: 'app-signup-form',
6 templateUrl: './signup-form.component.html',
7 styleUrls: ['./signup-form.component.scss']
8 })
10 gender = ['Male','Female','Other']
11
14 }
For the signup-form.component.html file, I am going to use the same HTML template discussed above,
but with minor changes. The signup form has a select field with a list of options. Although that works,
we will do it the Angular way by looping through the list using the ngFor directive.
app/signup-form/signup-form.component.html
4 <form class="form-horizontal">
5 <fieldset>
6 <legend>SignUp</legend>
7 .
8 .
10 <div class="form-group">
11 <label for="select">Gender</label>
12 <select id="select">
13
16 </option>
17 </select>
18 </div>
19 .
20 .
21 </fieldset>
22 </form>
23 </div>
24 </div>
Next, we want to bind the form data to the user class object so that when you enter the signup data into
the form, a new User object is created that temporarily stores that data. This way, you can keep the
view in sync with the model, and this is called binding.
There are a couple of ways to make this happen. Let me first introduce you to ngModel and ngForm.
ngForm and ngModel are Angular directives that are essential to creating template-driven forms. Let's
start with ngForm first. Here is an excerpt about ngForm from the Angular docs.
ngForm creates a top-level FormGroup instance and binds it to a <form> element to track aggregated
form value and validation status. As soon as you import FormsModule, this directive becomes active by
default on all <form> tags. You don't need to add a special selector.
app/signup-form/signup-form.component.html
1 <form
2 class="form-horizontal"
3 #signupForm = "ngForm">
4 .
5 .
6 </form>
#signupForm is a template reference variable that refers to the ngForm directive which governs the
entire form. The example below demonstrates the use of a ngForm reference object for validation.
app/signup-form/signup-form.component.html
1 <button
2 type="submit"
3 class="btn btn-success"
4 [disabled]="!signupForm.form.valid">
5 Submit
6 </button>
Here, signupForm.form.valid will return false unless all the form elements pass their respective
validation checks. The submit button will be disabled until the form is valid.
As for binding the template and the model, there are plenty of ways to do this, and ngModel has three
different syntaxes to tackle this situation. They are:
1. [(ngModel)]
2. [ngModel]
3. ngModel
[(ngModel)] performs two-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). The
image below describes the two-way binding process better.
1 <div class="form-group">
2 <label for="inputEmail">Email</label>
3 <input type="text"
4 [(ngModel)] = "user.email"
5 id="inputEmail"
6 name="email"
7 placeholder="Email">
8 </div>
[(ngModel)] = "user.email" binds the user's email property to the input value. I've also added a name
attribute and set name="email". This is important, and you will get an error if you've not declared a
name attribute while using ngModel.
Similarly, add a [(ngModel)] and a unique name attribute to each form element. Your form should look
something like this now:
app/signup-form/signup-form.component.html
1 .
2 .
3 .
4 <div ngModelGroup="password">
6 <label for="inputPassword">Password</label>
7 <input type="password"
9 placeholder="Password">
10 </div>
11 <div class="form-group">
13 <input type="password"
15 placeholder="Confirm Password">
16 </div>
17 </div>
18 <div class="form-group">
19 <label for="select">Gender</label>
20 <select id="select"
25 </option>
26 </select>
27 </div>
28
29 .
30 .
31 .
The ngModelGroup is used to group together similar form fields so that we can run validations only on
those form fields. Since both password fields are related, we will put them under a single
ngModelGroup. If everything is working as expected, the component-bound user property should be in
charge of storing all the form control values. To see this in action, add the following after the form tag:
1 {{user | json}}
Pipe the user property through the JsonPipe to render the model as JSON in the browser. This is helpful
for debugging and logging. You should see a JSON output like this.
The values are flowing in from the view to the model. What about the other way around? Try initializing
the user object with some values.
app/signup-form/signup-form.component.ts
1 newUser() {
3 }
1 { "email": "[email protected]",
3 "gender": "Male"
4 }
The two-way binding [(ngModel)] syntax helps you build forms effortlessly. However, it has certain
drawbacks; hence, there is an alternate approach that uses ngModel or [ngModel].
Advertisement
When ngModel is used, we are in fact responsible for updating the component property with the input
control values and vice versa. The input data doesn't automatically flow into the component's user
property.
So replace all instances of [(ngModel)] = " " with ngModel. We will keep the name attribute because all
three versions of ngModel need the name attribute to work.
app/signup-form/signup-form.component.html
1 <div class="form-group">
2 <label for="inputEmail">Email</label>
3 <input type="text"
4 ngModel
5 id="inputEmail"
6 name="email"
7 placeholder="Email">
8 </div>
With ngModel, the value of the name attribute will become a key of the ngForm reference object
signupForm that we created earlier. So, for example, signupForm.value.email will store the control value
for the email id.
Replace {{user | json}} with {{signupForm.value | json }} because that's where all the state is stored right
now.
What if you need to set the initial state from the bound class component? That's what the [ngModel]
does for you.
Here, the data flows from the model to the view. Make the following changes to the syntax to use one-
way binding:
app/signup-form/signup-form.component.html
1 <div class="form-group">
2 <label for="inputEmail">Email</label>
3 <input type="text"
4 [ngModel] = "user.email"
5 id="inputEmail"
6 name="email"
7 placeholder="Email">
8 </div>
So which approach should you choose? If you're using [(ngModel)] and ngForm together, you will
eventually have two states to maintain—user and signupForm.value—and that could be potentially
confusing.
1 { "email": "[email protected]",
3 "gender": "Male"
4 } //user.value
5 { "email": "[email protected]",
7 "gender": "Male"
8 } //signupForm.value
Hence, I will recommend using the one-way binding method instead. But that's something for you to
decide.
• Disable the submit button until all input fields are filled.
The first one is easy. You have to add a required validation attribute to each form element like this:
app/signup-form/signup-form.component.html
1 <input type="text"
3 #email = "ngModel"
4 placeholder="Email"
5 required>
Apart from the required attribute, I've also exported a new #email template reference variable. This is so
that you can access the input box's Angular control from within the template itself. We will use it to
display errors and warnings. Now use the button's disabled property to disable the button:
app/signup-form/signup-form.component.html
1 <button
2 type="submit"
3 class="btn btn-success"
4 [disabled]="!signupForm.form.valid">
5 Submit
6 </button>
To add a constraint on email, use the pattern attribute that works with input fields. Patterns are used to
specify regular expressions like the one below:
1 pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$"
For the password field, all you have to do is add a minlength=" " attribute:
app/signup-form/signup-form.component.html
1 <input type="password"
2 ngModel
3 id="inputPassword"
4 name="pwd"
5 #pwd = "ngModel"
6 placeholder="Password"
7 minlength="8"
8 required>
To display the errors, I am going to use the conditional directive ngIf on a div element. Let's start with
the input control field for email:
app/signup-form/signup-form.component.html
1 <div class="form-group">
2 <label for="inputEmail">Email</label>
3 <input type="text"
4 ost of our code will go. I've also created a new User.ts for storing our User
model.
Before we dive into the actual component template, we need to have an abstract idea of what we are
building. So here is the form structure that I have in my mind. The signup form will have several input
fields, a select element, and a checkbox element.
Here is the HTML template that we will be using for our registration page.
HTML Template
4 <form class="form-horizontal">
5 <fieldset>
6 <legend>SignUp</legend>
9 <div class="form-group">
10 <label for="inputEmail">Email</label>
11 <input type="text"
12 id="inputEmail"
13 placeholder="Email">
14 </div>
15 <!--- Password Block --->
16 <div class="form-group">
17 <label for="inputPassword">Password</label>
18 <input type="password"
19 id="inputPassword"
20 placeholder="Password">
21 </div>
22
23 <div class="form-group">
25 <input type="password"
26 id="confirmPassword"
27 placeholder="Password">
28 </div>
29
31 <div class="form-group">
32 <label for="select">Gender</label>
33 <select id="select">
34 <option>Male</option>
35 <option>Female</option>
36 <option>Other</option>
37 </select>
38 </div>
39
42 <label>
45 </label>
46 </div>
47
49 <div class="form-group">
52 </div>
53 </fieldset>
54 </form>
55 </div>
56 </div>
The CSS classes used in the HTML template are part of the Bootstrap library used for making things
pretty. Since this is a not a design tutorial, I won't be talking much about the CSS aspects of the form
unless necessary.
Advertisement
To use the template-driven form directives, we need to import the FormsModule from @angular/forms
and add it to the import array in app.module.ts.
app/app.module.ts
2 @NgModule({
3 .
4 .
5 imports: [
6 BrowserModule,
7 FormsModule
8 ],
9 .
10 .
11 })
12 export class AppModule { }
Next, create a class that will hold all properties of the User entity. We can either use an interface and
implement it in the component or use a TypeScript class for the model.
app/User.ts
2 constructor(
9 ){ }
10
11 }
4 @Component({
5 selector: 'app-signup-form',
6 templateUrl: './signup-form.component.html',
7 styleUrls: ['./signup-form.component.scss']
8 })
10 gender = ['Male','Female','Other']
11
13
14 }
For the signup-form.component.html file, I am going to use the same HTML template discussed above,
but with minor changes. The signup form has a select field with a list of options. Although that works,
we will do it the Angular way by looping through the list using the ngFor directive.
app/signup-form/signup-form.component.html
4 <form class="form-horizontal">
5 <fieldset>
6 <legend>SignUp</legend>
7 .
8 .
10 <div class="form-group">
11 <label for="select">Gender</label>
12 <select id="select">
13
14 <option *ngFor = "let g of gender"
16 </option>
17 </select>
18 </div>
19 .
20 .
21 </fieldset>
22 </form>
23 </div>
24 </div>
Next, we want to bind the form data to the user class object so that when you enter the signup data into
the form, a new User object is created that temporarily stores that data. This way, you can keep the
view in sync with the model, and this is called binding.
There are a couple of ways to make this happen. Let me first introduce you to ngModel and ngForm.
ngForm creates a top-level FormGroup instance and binds it to a <form> element to track aggregated
form value and validation status. As soon as you import FormsModule, this directive becomes active by
default on all <form> tags. You don't need to add a special selector.
app/signup-form/signup-form.component.html
1 <form
2 class="form-horizontal"
3 #signupForm = "ngForm">
4 .
5 .
6 </form>
#signupForm is a template reference variable that refers to the ngForm directive which governs the
entire form. The example below demonstrates the use of a ngForm reference object for validation.
app/signup-form/signup-form.component.html
1 <button
2 type="submit"
3 class="btn btn-success"
4 [disabled]="!signupForm.form.valid">
5 Submit
6 </button>
Here, signupForm.form.valid will return false unless all the form elements pass their respective
validation checks. The submit button will be disabled until the form is valid.
As for binding the template and the model, there are plenty of ways to do this, and ngModel has three
different syntaxes to tackle this situation. They are:
1. [(ngModel)]
2. [ngModel]
3. ngModel
[(ngModel)] performs two-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). The
image below describes the two-way binding process better.
1 <div class="form-group">
2 <label for="inputEmail">Email</label>
3 <input type="text"
4 [(ngModel)] = "user.email"
5 id="inputEmail"
6 name="email"
7 placeholder="Email">
8 </div>
[(ngModel)] = "user.email" binds the user's email property to the input value. I've also added a name
attribute and set name="email". This is important, and you will get an error if you've not declared a
name attribute while using ngModel.
Similarly, add a [(ngModel)] and a unique name attribute to each form element. Your form should look
something like this now:
app/signup-form/signup-form.component.html
1 .
2 .
3 .
4 <div ngModelGroup="password">
6 <label for="inputPassword">Password</label>
7 <input type="password"
10 </div>
11 <div class="form-group">
13 <input type="password"
15 placeholder="Confirm Password">
16 </div>
17 </div>
18 <div class="form-group">
19 <label for="select">Gender</label>
20 <select id="select"
22
25 </option>
26 </select>
27 </div>
28
29 .
30 .
31 .
The ngModelGroup is used to group together similar form fields so that we can run validations only on
those form fields. Since both password fields are related, we will put them under a single
ngModelGroup. If everything is working as expected, the component-bound user property should be in
charge of storing all the form control values. To see this in action, add the following after the form tag:
1 {{user | json}}
Pipe the user property through the JsonPipe to render the model as JSON in the browser. This is helpful
for debugging and logging. You should see a JSON output like this.
The values are flowing in from the view to the model. What about the other way around? Try initializing
the user object with some values.
app/signup-form/signup-form.component.ts
1 newUser() {
3 }
1 { "email": "[email protected]",
3 "gender": "Male"
4 }
The two-way binding [(ngModel)] syntax helps you build forms effortlessly. However, it has certain
drawbacks; hence, there is an alternate approach that uses ngModel or [ngModel].
Advertisement
When ngModel is used, we are in fact responsible for updating the component property with the input
control values and vice versa. The input data doesn't automatically flow into the component's user
property.
So replace all instances of [(ngModel)] = " " with ngModel. We will keep the name attribute because all
three versions of ngModel need the name attribute to work.
app/signup-form/signup-form.component.html
1 <div class="form-group">
2 <label for="inputEmail">Email</label>
3 <input type="text"
4 ngModel
5 id="inputEmail"
6 name="email"
7 placeholder="Email">
8 </div>
With ngModel, the value of the name attribute will become a key of the ngForm reference object
signupForm that we created earlier. So, for example, signupForm.value.email will store the control value
for the email id.
Replace {{user | json}} with {{signupForm.value | json }} because that's where all the state is stored right
now.
What if you need to set the initial state from the bound class component? That's what the [ngModel]
does for you.
Here, the data flows from the model to the view. Make the following changes to the syntax to use one-
way binding:
app/signup-form/signup-form.component.html
1 <div class="form-group">
2 <label for="inputEmail">Email</label>
3 <input type="text"
4 [ngModel] = "user.email"
5 id="inputEmail"
6 name="email"
7 placeholder="Email">
8 </div>
So which approach should you choose? If you're using [(ngModel)] and ngForm together, you will
eventually have two states to maintain—user and signupForm.value—and that could be potentially
confusing.
1 { "email": "[email protected]",
3 "gender": "Male"
4 } //user.value
5 { "email": "[email protected]",
8 } //signupForm.value
Hence, I will recommend using the one-way binding method instead. But that's something for you to
decide.
• Disable the submit button until all input fields are filled.
The first one is easy. You have to add a required validation attribute to each form element like this:
app/signup-form/signup-form.component.html
1 <input type="text"
3 #email = "ngModel"
4 placeholder="Email"
5 required>
Apart from the required attribute, I've also exported a new #email template reference variable. This is so
that you can access the input box's Angular control from within the template itself. We will use it to
display errors and warnings. Now use the button's disabled property to disable the button:
app/signup-form/signup-form.component.html
1 <button
2 type="submit"
3 class="btn btn-success"
4 [disabled]="!signupForm.form.valid">
5 Submit
6 </button>
To add a constraint on email, use the pattern attribute that works with input fields. Patterns are used to
specify regular expressions like the one below:
1 pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$"
For the password field, all you have to do is add a minlength=" " attribute:
app/signup-form/signup-form.component.html
1 <input type="password"
2 ngModel
3 id="inputPassword"
4 name="pwd"
5 #pwd = "ngModel"
6 placeholder="Password"
7 minlength="8"
8 required>
To display the errors, I am going to use the conditional directive ngIf on a div element. Let's start with
the input control field for email:
app/signup-form/signup-form.component.html
1 <div class="form-group">
2 <label for="inputEmail">Email</label>
3 <input type="text"
6 placeholder="Email"
7 pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$"
8 required>
9 </div>
12 class="alert alert-danger">
15 </div>
18 </div>
19 </div>
There is a lot going on here. Let's start with the first line of the error section.
2 class="alert alert-danger"
Remember the #email variable that we exported earlier? It carries some amount of information about
the input control state of the email field. This includes email.valid, email.invalid, email.dirty,
email.pristine, email.touched, email.untouched, and email.errors. The image below describes each of
those properties in detail.
So the div element with the *ngIf will be rendered only if the email is invalid. However, the user will be
greeted with errors about the input fields being blank even before they have a chance to edit the form.
To avoid this scenario, we've added the second condition. The error will be displayed only after the
control has been visited or the control's value has been changed.
The nested div elements are used to cover all the cases of validation errors. We use email.errors to
check all possible validation errors and then display them back to the user in the form of custom
messages. Now, follow the same procedure for the other form elements. Here is how I've coded the
validation for the passwords.
app/signup-form/signup-form.component.html
2 <div class="form-group">
3 <label for="inputPassword">Password</label>
4 <input type="password"
5 ngModel name="pwd"
6 id="inputPassword" placeholder="Password"
8 </div>
9 <div class="form-group">
10 <label for="confirmPassword" >Confirm Password</label>
11 <input type="password"
12 ngModel name="confirmPwd"
14 </div>
15
16
18 class="alert alert-danger">
19
22 </div>
25 </ng-template>
26 </div>
27 </div>
This is starting to look a bit messy. Angular has a limited set of validator attributes: required, minlength,
maxlength, and pattern. To cover any other scenario like that of password comparison, you will have to
rely on nested ngIf conditionals as I did above. Or ideally, create a custom validator function, which I will
cover in the third part of this series.
In the code above, I've used the ngIf else syntax which was introduced in the latest version of Angular.
Here is how it works:
2 Valid content...
3 </div>
We have nearly finished the form. Now we need to be able to submit the form, and the control of the
form data should be handed over to a component method, say onSubmit.
app/signup-form/signup-form.component.html
3 (ngSubmit)= "onSubmit(signupForm)"
4 #signupForm="ngForm">
app/signup-form/signup-form.component.ts
ost of our code will go. I've also created a new User.ts for storing our User model.
Before we dive into the actual component template, we need to have an abstract idea of what we are
building. So here is the form structure that I have in my mind. The signup form will have several input
fields, a select element, and a checkbox element.
Here is the HTML template that we will be using for our registration page.
HTML Template
<form class="form-horizontal">
<fieldset>
<legend>SignUp</legend>
<!--- Email Block --->
<div class="form-group">
<label for="inputEmail">Email</label>
<input type="text"
id="inputEmail"
placeholder="Email">
</div>
<div class="form-group">
<label for="inputPassword">Password</label>
<input type="password"
id="inputPassword"
placeholder="Password">
</div>
<div class="form-group">
<input type="password"
id="confirmPassword"
placeholder="Password">
</div>
<div class="form-group">
<label for="select">Gender</label>
<select id="select">
<option>Male</option>
<option>Female</option>
<option>Other</option>
</select>
</div>
<label>
Conditions
</label>
</div>
<div class="form-group">
</div>
</fieldset>
</form>
</div>
</div>
The CSS classes used in the HTML template are part of the Bootstrap library used for making things
pretty. Since this is a not a design tutorial, I won't be talking much about the CSS aspects of the form
unless necessary.
Advertisement
app/app.module.ts
@NgModule({
imports: [
BrowserModule,
FormsModule
],
})
Next, create a class that will hold all properties of the User entity. We can either use an interface and
implement it in the component or use a TypeScript class for the model.
app/User.ts
constructor(
app/signup-form/signup-form.component.ts
@Component({
selector: 'app-signup-form',
templateUrl: './signup-form.component.html',
styleUrls: ['./signup-form.component.scss']
})
gender = ['Male','Female','Other']
For the signup-form.component.html file, I am going to use the same HTML template discussed above,
but with minor changes. The signup form has a select field with a list of options. Although that works,
we will do it the Angular way by looping through the list using the ngFor directive.
app/signup-form/signup-form.component.html
<form class="form-horizontal">
<fieldset>
<legend>SignUp</legend>
<div class="form-group">
<label for="select">Gender</label>
<select id="select">
</option>
</select>
</div>
</fieldset>
</form>
</div>
</div>
Next, we want to bind the form data to the user class object so that when you enter the signup data into
the form, a new User object is created that temporarily stores that data. This way, you can keep the
view in sync with the model, and this is called binding.
There are a couple of ways to make this happen. Let me first introduce you to ngModel and ngForm.
ngForm and ngModel are Angular directives that are essential to creating template-driven forms. Let's
start with ngForm first. Here is an excerpt about ngForm from the Angular docs.
ngForm creates a top-level FormGroup instance and binds it to a <form> element to track aggregated
form value and validation status. As soon as you import FormsModule, this directive becomes active by
default on all <form> tags. You don't need to add a special selector.
app/signup-form/signup-form.component.html
<form
class="form-horizontal"
#signupForm = "ngForm">
</form>
#signupForm is a template reference variable that refers to the ngForm directive which governs the
entire form. The example below demonstrates the use of a ngForm reference object for validation.
app/signup-form/signup-form.component.html
<button
type="submit"
class="btn btn-success"
[disabled]="!signupForm.form.valid">
Submit
</button>
Here, signupForm.form.valid will return false unless all the form elements pass their respective
validation checks. The submit button will be disabled until the form is valid.
As for binding the template and the model, there are plenty of ways to do this, and ngModel has three
different syntaxes to tackle this situation. They are:
[(ngModel)]
[ngModel]
ngModel
[(ngModel)] performs two-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). The
image below describes the two-way binding process better.
<div class="form-group">
<label for="inputEmail">Email</label>
<input type="text"
[(ngModel)] = "user.email"
id="inputEmail"
name="email"
placeholder="Email">
</div>
[(ngModel)] = "user.email" binds the user's email property to the input value. I've also added a name
attribute and set name="email". This is important, and you will get an error if you've not declared a
name attribute while using ngModel.
Similarly, add a [(ngModel)] and a unique name attribute to each form element. Your form should look
something like this now:
app/signup-form/signup-form.component.html
.
<div ngModelGroup="password">
<label for="inputPassword">Password</label>
<input type="password"
placeholder="Password">
</div>
<div class="form-group">
<input type="password"
placeholder="Confirm Password">
</div>
</div>
<div class="form-group">
<label for="select">Gender</label>
<select id="select"
</option>
</select>
</div>
.
.
The ngModelGroup is used to group together similar form fields so that we can run validations only on
those form fields. Since both password fields are related, we will put them under a single
ngModelGroup. If everything is working as expected, the component-bound user property should be in
charge of storing all the form control values. To see this in action, add the following after the form tag:
{{user | json}}
Pipe the user property through the JsonPipe to render the model as JSON in the browser. This is helpful
for debugging and logging. You should see a JSON output like this.
The values are flowing in from the view to the model. What about the other way around? Try initializing
the user object with some values.
app/signup-form/signup-form.component.ts
newUser() {
{ "email": "[email protected]",
"gender": "Male"
The two-way binding [(ngModel)] syntax helps you build forms effortlessly. However, it has certain
drawbacks; hence, there is an alternate approach that uses ngModel or [ngModel].
Advertisement
When ngModel is used, we are in fact responsible for updating the component property with the input
control values and vice versa. The input data doesn't automatically flow into the component's user
property.
So replace all instances of [(ngModel)] = " " with ngModel. We will keep the name attribute because all
three versions of ngModel need the name attribute to work.
app/signup-form/signup-form.component.html
<div class="form-group">
<label for="inputEmail">Email</label>
<input type="text"
ngModel
id="inputEmail"
name="email"
placeholder="Email">
</div>
With ngModel, the value of the name attribute will become a key of the ngForm reference object
signupForm that we created earlier. So, for example, signupForm.value.email will store the control value
for the email id.
Replace {{user | json}} with {{signupForm.value | json }} because that's where all the state is stored right
now.
What if you need to set the initial state from the bound class component? That's what the [ngModel]
does for you.
Here, the data flows from the model to the view. Make the following changes to the syntax to use one-
way binding:
app/signup-form/signup-form.component.html
<div class="form-group">
<label for="inputEmail">Email</label>
<input type="text"
[ngModel] = "user.email"
id="inputEmail"
name="email"
placeholder="Email">
</div>
So which approach should you choose? If you're using [(ngModel)] and ngForm together, you will
eventually have two states to maintain—user and signupForm.value—and that could be potentially
confusing.
{ "email": "[email protected]",
"gender": "Male"
} //user.value
{ "email": "[email protected]",
"gender": "Male"
} //signupForm.value
Hence, I will recommend using the one-way binding method instead. But that's something for you to
decide.
Validation and Displaying Error Messages
Disable the submit button until all input fields are filled.
The first one is easy. You have to add a required validation attribute to each form element like this:
app/signup-form/signup-form.component.html
<input type="text"
#email = "ngModel"
placeholder="Email"
required>
Apart from the required attribute, I've also exported a new #email template reference variable. This is so
that you can access the input box's Angular control from within the template itself. We will use it to
display errors and warnings. Now use the button's disabled property to disable the button:
app/signup-form/signup-form.component.html
<button
type="submit"
class="btn btn-success"
[disabled]="!signupForm.form.valid">
Submit
</button>
To add a constraint on email, use the pattern attribute that works with input fields. Patterns are used to
specify regular expressions like the one below:
pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$"
For the password field, all you have to do is add a minlength=" " attribute:
app/signup-form/signup-form.component.html
<input type="password"
ngModel
id="inputPassword"
name="pwd"
#pwd = "ngModel"
placeholder="Password"
minlength="8"
required>
To display the errors, I am going to use the conditional directive ngIf on a div element. Let's start with
the input control field for email:
app/signup-form/signup-form.component.html
<div class="form-group">
<label for="inputEmail">Email</label>
<input type="text"
pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$"
required>
</div>
class="alert alert-danger">
</div>
</div>
</div>
There is a lot going on here. Let's start with the first line of the error section.
class="alert alert-danger"
Remember the #email variable that we exported earlier? It carries some amount of information about
the input control state of the email field. This includes email.valid, email.invalid, email.dirty,
email.pristine, email.touched, email.untouched, and email.errors. The image below describes each of
those properties in detail.
So the div element with the *ngIf will be rendered only if the email is invalid. However, the user will be
greeted with errors about the input fields being blank even before they have a chance to edit the form.
To avoid this scenario, we've added the second condition. The error will be displayed only after the
control has been visited or the control's value has been changed.
The nested div elements are used to cover all the cases of validation errors. We use email.errors to
check all possible validation errors and then display them back to the user in the form of custom
messages. Now, follow the same procedure for the other form elements. Here is how I've coded the
validation for the passwords.
app/signup-form/signup-form.component.html
<div class="form-group">
<label for="inputPassword">Password</label>
<input type="password"
ngModel name="pwd"
id="inputPassword" placeholder="Password"
</div>
<div class="form-group">
<input type="password"
ngModel name="confirmPwd"
</div>
class="alert alert-danger">
</div>
</ng-template>
</div>
</div>
This is starting to look a bit messy. Angular has a limited set of validator attributes: required, minlength,
maxlength, and pattern. To cover any other scenario like that of password comparison, you will have to
rely on nested ngIf conditionals as I did above. Or ideally, create a custom validator function, which I will
cover in the third part of this series.
In the code above, I've used the ngIf else syntax which was introduced in the latest version of Angular.
Here is how it works:
Valid content...
</div>
We have nearly finished the form. Now we need to be able to submit the form, and the control of the
form data should be handed over to a component method, say onSubmit.
app/signup-form/signup-form.component.html
onFormSubmit(signupForm)
(ngSubmit)= "onSubmit(signupForm)"
#signupForm="ngForm">
Now, for the component:
app/signup-form/signup-form.component.ts
console.log( this.user.email);
Final Demo
I've put the final version of the application in a GitHub repo. You can download or clone it to try it out
for yourself. I've added a few bootstrap classes to make the form pretty.
Summary
2 console.log( this.user.email);
4 }
Final Demo
I've put the final version of the application in a GitHub repo. You can download or clone it to try it out
for yourself. I've added a few bootstrap classes to make the form pretty.
Summary
7 pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$"
8 required>
9 </div>
12 class="alert alert-danger">
15 </div>
18 </div>
19 </div>
There is a lot going on here. Let's start with the first line of the error section.
2 class="alert alert-danger"
Remember the #email variable that we exported earlier? It carries some amount of information about
the input control state of the email field. This includes email.valid, email.invalid, email.dirty,
email.pristine, email.touched, email.untouched, and email.errors. The image below describes each of
those properties in detail.
So the div element with the *ngIf will be rendered only if the email is invalid. However, the user will be
greeted with errors about the input fields being blank even before they have a chance to edit the form.
To avoid this scenario, we've added the second condition. The error will be displayed only after the
control has been visited or the control's value has been changed.
The nested div elements are used to cover all the cases of validation errors. We use email.errors to
check all possible validation errors and then display them back to the user in the form of custom
messages. Now, follow the same procedure for the other form elements. Here is how I've coded the
validation for the passwords.
app/signup-form/signup-form.component.html
2 <div class="form-group">
3 <label for="inputPassword">Password</label>
4 <input type="password"
5 ngModel name="pwd"
6 id="inputPassword" placeholder="Password"
7 minlength ="8" required>
8 </div>
9 <div class="form-group">
11 <input type="password"
12 ngModel name="confirmPwd"
14 </div>
15
16
18 class="alert alert-danger">
19
ost of our code will go. I've also created a new User.ts for storing our User model.
Here is the HTML template that we will be using for our registration page.
HTML Template
<form class="form-horizontal">
<fieldset>
<legend>SignUp</legend>
<div class="form-group">
<label for="inputEmail">Email</label>
<input type="text"
id="inputEmail"
placeholder="Email">
</div>
<div class="form-group">
<label for="inputPassword">Password</label>
<input type="password"
id="inputPassword"
placeholder="Password">
</div>
<div class="form-group">
<input type="password"
id="confirmPassword"
placeholder="Password">
</div>
<div class="form-group">
<label for="select">Gender</label>
<select id="select">
<option>Male</option>
<option>Female</option>
<option>Other</option>
</select>
</div>
<label>
Conditions
</label>
</div>
<div class="form-group">
<button type="reset" class="btn btn-default">Cancel</button>
</div>
</fieldset>
</form>
</div>
</div>
The CSS classes used in the HTML template are part of the Bootstrap library used for making things
pretty. Since this is a not a design tutorial, I won't be talking much about the CSS aspects of the form
unless necessary.
Advertisement
To use the template-driven form directives, we need to import the FormsModule from @angular/forms
and add it to the import array in app.module.ts.
app/app.module.ts
@NgModule({
imports: [
BrowserModule,
FormsModule
],
})
export class AppModule { }
Next, create a class that will hold all properties of the User entity. We can either use an interface and
implement it in the component or use a TypeScript class for the model.
app/User.ts
constructor(
){ }
app/signup-form/signup-form.component.ts
@Component({
selector: 'app-signup-form',
templateUrl: './signup-form.component.html',
styleUrls: ['./signup-form.component.scss']
})
gender = ['Male','Female','Other']
user = new User(1,'[email protected]','secret', 'secret', this.gender[1], true);
For the signup-form.component.html file, I am going to use the same HTML template discussed above,
but with minor changes. The signup form has a select field with a list of options. Although that works,
we will do it the Angular way by looping through the list using the ngFor directive.
app/signup-form/signup-form.component.html
<form class="form-horizontal">
<fieldset>
<legend>SignUp</legend>
<div class="form-group">
<label for="select">Gender</label>
<select id="select">
</option>
</select>
</div>
.
</fieldset>
</form>
</div>
</div>
Next, we want to bind the form data to the user class object so that when you enter the signup data into
the form, a new User object is created that temporarily stores that data. This way, you can keep the
view in sync with the model, and this is called binding.
There are a couple of ways to make this happen. Let me first introduce you to ngModel and ngForm.
ngForm and ngModel are Angular directives that are essential to creating template-driven forms. Let's
start with ngForm first. Here is an excerpt about ngForm from the Angular docs.
ngForm creates a top-level FormGroup instance and binds it to a <form> element to track aggregated
form value and validation status. As soon as you import FormsModule, this directive becomes active by
default on all <form> tags. You don't need to add a special selector.
app/signup-form/signup-form.component.html
<form
class="form-horizontal"
#signupForm = "ngForm">
</form>
#signupForm is a template reference variable that refers to the ngForm directive which governs the
entire form. The example below demonstrates the use of a ngForm reference object for validation.
app/signup-form/signup-form.component.html
<button
type="submit"
class="btn btn-success"
[disabled]="!signupForm.form.valid">
Submit
</button>
Here, signupForm.form.valid will return false unless all the form elements pass their respective
validation checks. The submit button will be disabled until the form is valid.
As for binding the template and the model, there are plenty of ways to do this, and ngModel has three
different syntaxes to tackle this situation. They are:
[(ngModel)]
[ngModel]
ngModel
[(ngModel)] performs two-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). The
image below describes the two-way binding process better.
<div class="form-group">
<label for="inputEmail">Email</label>
<input type="text"
[(ngModel)] = "user.email"
id="inputEmail"
name="email"
placeholder="Email">
</div>
[(ngModel)] = "user.email" binds the user's email property to the input value. I've also added a name
attribute and set name="email". This is important, and you will get an error if you've not declared a
name attribute while using ngModel.
Similarly, add a [(ngModel)] and a unique name attribute to each form element. Your form should look
something like this now:
app/signup-form/signup-form.component.html
<div ngModelGroup="password">
<label for="inputPassword">Password</label>
<input type="password"
placeholder="Password">
</div>
<div class="form-group">
<input type="password"
placeholder="Confirm Password">
</div>
</div>
<div class="form-group">
<label for="select">Gender</label>
<select id="select"
</option>
</select>
</div>
The ngModelGroup is used to group together similar form fields so that we can run validations only on
those form fields. Since both password fields are related, we will put them under a single
ngModelGroup. If everything is working as expected, the component-bound user property should be in
charge of storing all the form control values. To see this in action, add the following after the form tag:
{{user | json}}
Pipe the user property through the JsonPipe to render the model as JSON in the browser. This is helpful
for debugging and logging. You should see a JSON output like this.
The values are flowing in from the view to the model. What about the other way around? Try initializing
the user object with some values.
app/signup-form/signup-form.component.ts
newUser() {
{ "email": "[email protected]",
"gender": "Male"
The two-way binding [(ngModel)] syntax helps you build forms effortlessly. However, it has certain
drawbacks; hence, there is an alternate approach that uses ngModel or [ngModel].
Advertisement
When ngModel is used, we are in fact responsible for updating the component property with the input
control values and vice versa. The input data doesn't automatically flow into the component's user
property.
So replace all instances of [(ngModel)] = " " with ngModel. We will keep the name attribute because all
three versions of ngModel need the name attribute to work.
app/signup-form/signup-form.component.html
<div class="form-group">
<label for="inputEmail">Email</label>
<input type="text"
ngModel
id="inputEmail"
name="email"
placeholder="Email">
</div>
With ngModel, the value of the name attribute will become a key of the ngForm reference object
signupForm that we created earlier. So, for example, signupForm.value.email will store the control value
for the email id.
Replace {{user | json}} with {{signupForm.value | json }} because that's where all the state is stored right
now.
What if you need to set the initial state from the bound class component? That's what the [ngModel]
does for you.
Here, the data flows from the model to the view. Make the following changes to the syntax to use one-
way binding:
app/signup-form/signup-form.component.html
<div class="form-group">
<label for="inputEmail">Email</label>
<input type="text"
[ngModel] = "user.email"
id="inputEmail"
name="email"
placeholder="Email">
</div>
So which approach should you choose? If you're using [(ngModel)] and ngForm together, you will
eventually have two states to maintain—user and signupForm.value—and that could be potentially
confusing.
{ "email": "[email protected]",
"gender": "Male"
} //user.value
{ "email": "[email protected]",
"gender": "Male"
} //signupForm.value
Hence, I will recommend using the one-way binding method instead. But that's something for you to
decide.
Disable the submit button until all input fields are filled.
The first one is easy. You have to add a required validation attribute to each form element like this:
app/signup-form/signup-form.component.html
<input type="text"
#email = "ngModel"
placeholder="Email"
required>
Apart from the required attribute, I've also exported a new #email template reference variable. This is so
that you can access the input box's Angular control from within the template itself. We will use it to
display errors and warnings. Now use the button's disabled property to disable the button:
app/signup-form/signup-form.component.html
<button
type="submit"
class="btn btn-success"
[disabled]="!signupForm.form.valid">
Submit
</button>
To add a constraint on email, use the pattern attribute that works with input fields. Patterns are used to
specify regular expressions like the one below:
pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$"
For the password field, all you have to do is add a minlength=" " attribute:
app/signup-form/signup-form.component.html
<input type="password"
ngModel
id="inputPassword"
name="pwd"
#pwd = "ngModel"
placeholder="Password"
minlength="8"
required>
To display the errors, I am going to use the conditional directive ngIf on a div element. Let's start with
the input control field for email:
app/signup-form/signup-form.component.html
<div class="form-group">
<label for="inputEmail">Email</label>
<input type="text"
placeholder="Email"
pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$"
required>
</div>
class="alert alert-danger">
</div>
</div>
</div>
There is a lot going on here. Let's start with the first line of the error section.
class="alert alert-danger"
Remember the #email variable that we exported earlier? It carries some amount of information about
the input control state of the email field. This includes email.valid, email.invalid, email.dirty,
email.pristine, email.touched, email.untouched, and email.errors. The image below describes each of
those properties in detail.
So the div element with the *ngIf will be rendered only if the email is invalid. However, the user will be
greeted with errors about the input fields being blank even before they have a chance to edit the form.
To avoid this scenario, we've added the second condition. The error will be displayed only after the
control has been visited or the control's value has been changed.
The nested div elements are used to cover all the cases of validation errors. We use email.errors to
check all possible validation errors and then display them back to the user in the form of custom
messages. Now, follow the same procedure for the other form elements. Here is how I've coded the
validation for the passwords.
app/signup-form/signup-form.component.html
<div class="form-group">
<label for="inputPassword">Password</label>
<input type="password"
ngModel name="pwd"
id="inputPassword" placeholder="Password"
</div>
<div class="form-group">
<label for="confirmPassword" >Confirm Password</label>
<input type="password"
ngModel name="confirmPwd"
</div>
class="alert alert-danger">
</div>
</ng-template>
</div>
</div>
This is starting to look a bit messy. Angular has a limited set of validator attributes: required, minlength,
maxlength, and pattern. To cover any other scenario like that of password comparison, you will have to
rely on nested ngIf conditionals as I did above. Or ideally, create a custom validator function, which I will
cover in the third part of this series.
In the code above, I've used the ngIf else syntax which was introduced in the latest version of Angular.
Here is how it works:
Valid content...
</div>
We have nearly finished the form. Now we need to be able to submit the form, and the control of the
form data should be handed over to a component method, say onSubmit.
app/signup-form/signup-form.component.html
onFormSubmit(signupForm)
(ngSubmit)= "onSubmit(signupForm)"
#signupForm="ngForm">
app/signup-form/signup-form.component.ts
console.log( this.user.email);
Final Demo
I've put the final version of the application in a GitHub repo. You can download or clone it to try it out
for yourself. I've added a few bootstrap classes to make the form pretty.
Summary
25 </ng-template>
26 </div>
27 </div>
This is starting to look a bit messy. Angular has a limited set of validator attributes: required, minlength,
maxlength, and pattern. To cover any other scenario like that of password comparison, you will have to
rely on nested ngIf conditionals as I did above. Or ideally, create a custom validator function, which I will
cover in the third part of this series.
In the code above, I've used the ngIf else syntax which was introduced in the latest version of Angular.
Here is how it works:
2 Valid content...
3 </div>
app/signup-form/signup-form.component.html
2 onFormSubmit(signupForm)
3 (ngSubmit)= "onSubmit(signupForm)"
4 #signupForm="ngForm">
app/signup-form/signup-form.component.ts
2 console.log( this.user.email);
4 }
Final Demo
I've put the final version of the application in a GitHub repo. You can download or clone it to try it out
for yourself. I've added a few bootstrap classes to make the form pretty.
Summary