Angular (v2+) presents two different methods for creating forms, template-driven (what we were used to in AngularJS 1.x), or reactive. We’re going to explore the absolute fundamentals of the template-driven Angular forms, covering
ngModelGroup, submit events, validation and error messages.
Table of contents
Before we begin, let’s clarify what “template-driven” forms mean from a high level.
When we talk about “template-driven” forms, we’ll actually be talking about the kind of forms we’re used to with AngularJS, whereby we bind directives and behaviour to our templates, and let Angular roll with it. Examples of these directives we’d use are
ngModel and perhaps
minlength and so forth. On a high-level, this is what template-driven forms achieve for us - by specifying directives to bind our models, values, validation and so on, we are letting the template do the work under the scenes.
Form base and interface
I’m a poet and didn’t know it. Anyway, here’s the form structure that we’ll be using to implement our template-driven form:
We have three inputs, the first, the user’s name, followed by a grouped set of inputs that take the user’s email address.
Things we’ll implement:
- Bind to the user’s
- Required validation on all inputs
- Show required validation errors
- Disabling submit until valid
- Submit function
Secondly, we’ll be implementing this interface:
ngModule and template-driven forms
Before we even dive into template-driven forms, we need to tell our
@NgModule to use the
You will obviously need to wire up all your other dependencies in the correct
FormsModulefor template-driven, and
ReactiveFormsModulefor reactive forms.
With template-driven forms, we can essentially leave a component class empty until we need to read/write values (such as submit and setting initial or future data). Let’s start with a base
SignupFormComponent and our above template:
So, this is a typical component base that we need to get going. So what now? Well, to begin with, we don’t need to actually create any initial “data”, however, we will import our
User interface and assign it to a public variable to kick things off:
Now we’re ready. So, what was the purpose of what we just did with
public user: User;? We’re binding a model that must adhere to the interface we created. Now we’re ready to tell our template-driven form what to do, to update and power that Object.
Binding ngForm and ngModel
Our first task is “Bind to the user’s name, email, and confirm inputs”.
So let’s get started. What do we bind with? You guessed it, our beloved friends
ngModel. Let’s start with
<form> we are exporting the
ngForm value to a public
#f variable, to which we can render out the value of the form.
#fis the exported form Object, so think of this as the generated output to your model’s input.
Let’s see what that would output for us when using
There is a lot going on under the hood with
ngFormwhich for the most part you do not need to know about to use template-driven forms but if you want more information, you can read about it here
Here we get an empty Object as our form value has no models, so nothing will be logged out. This is where we create nested bindings inside the same form so Angular can look out for them. Now we’re ready to bind some models, but first there are a few different
ngModel flavours we can roll with - so let’s break them down.
ngModel, [ngModel] and [(ngModel)]
ngModel syntaxes, are we going insane? Nah, this is awesome sauce, trust me. Let’s dive into each one.
- ngModel = if no binding or value is assigned,
ngModelwill look for a
nameattribute and assign that value as a new Object key to the global
However, this will actually throw an error as we need a
name="" attribute for all our form fields:
ngModel“talks to” the form, and binds the form value based on the
nameattribute’s value. In this case
name="name". Therefore it is needed.
Output from this at runtime:
Woo! Our first binding. But what if we want to set initial data?
- [ngModel] = one-way binding syntax, can set initial data from the bound component class, but will bind based on the
Some initial data for our
We can then simply bind
user.name from our component class to the
Output from this at runtime:
So this allows us to set some initial data from
this.user.name, which automagically binds and outputs to
Note: The actual value of
this.user.nameis never updated upon form changes, this is one-way dataflow. Form changes from ngModel are exported onto the respectived
It’s important to note that
[ngModel] is in fact a model setter. This is ideally the approach you’d want to take instead of two-way binding.
- [(ngModel)] = two-way binding syntax, can set initial data from the bound component class, but also update it:
Output from this (upon typing, both are reflected with changes):
This isn’t such a great idea, as we now have two separate states to keep track of inside the form component. Ideally, you’d implement one-way databinding and let the
ngForm do all the work here.
Side note, these two implementations are equivalents:
[(ngModel)] syntax is sugar syntax for masking the
(ngModelChange) event setter, that’s it.
ngModels and ngModelGroup
So now we’ve covered some intricacies of
ngModel, let’s hook up the rest of the template-driven form. We have a nested
account property on our
user Object, that accepts an
confirm value. To wire these up, we can introduce
ngModelGroup to essentially created a nested group of
This creates a nice structure based on the representation in the DOM that pseudo-looks like this:
Which matches up nicely with our
this.user interface, and the runtime output:
This is why they’re called template-driven. So what next? Let’s add some submit functionality.
To wire up a submit event, all we need to do is add a
ngSubmit event directive to our form:
Notice how we just passed
f into the
onSubmit()? This allows us to pull down various pieces of information from our respective method on our component class:
Here we’re using Object destructuring to fetch the
valid properties from that
#f reference we exported and passed into
value is basically everything we saw from above when we parsed out the
f.value in the DOM. That’s literally it, you’re free to pass values to your backend API.
Template-driven error validation
Oh la la, the fancy bits. To roll out some validation is actually very similar to how we’d approach this in AngularJS 1.x as well (hooking into individual form field validation properties).
First off, let’s start simple and disable our submit button until the form’s valid:
Here we’re binding to the
disabled property of the button, and setting it to
true dynamically when
f.invalid is true. When the form is
valid, the submit curse shall be lifted and allow submission.
required attributes on each
So, onto displaying errors. We have access to
#f, which we can log out as
f.value. Now, one thing we haven’t touched on is the inner workings of these magical
ngModelGroup directives. They actually, internally, spin up their own Form Controls and other gadgets. When it comes to referencing these controls, we must use the
.controls property on the Object. Let’s say we want to show if there are any errors on the
name property of our form:
Note how we’ve used
f.controls.name here, followed by the
?.errors. This is a safeguard mechanism to essentially tell Angular that this property might not exist yet, but render it out if it does. Similarly if the value becomes
undefined again, the error is not thrown.
?.propis called the “Safe navigation operator”
Let’s move onto setting up an error field for our form by adding the following error box to our
Okay, this looks a little messy and is error prone if we begin to extend our forms with more nested Objects and data. Let’s fix that by exporting a new
#userName variable from the input itself based on the
Now, this shows the error message at runtime, which we don’t want to alarm users with. What we can do is add some
userName.touched into the mix:
And we’re good.
trueonce the user has blurred the input, which may be a relevant time to show the error if they’ve not filled anything out
Let’s add a
minlength attribute just because:
We can then replicate this validation setup now on the other inputs:
Tip: it may be ideal to minimise model reference exporting and inline validation, and move the validation to the
Let’s explore cutting down our validation for
confirm fields (inside our
ngModelGroup) and create a group-specific validation messages if that makes sense for the group of fields.
To do this, we can export a reference to the
ngModelGroup by using
#userAccount="ngModelGroup", and adjusting our validation messages to the following:
We’ve also removed both
We’re all done for this tutorial. Keep an eye out for custom validation, reactive forms and much more. Here’s the fully working final code from what we’ve covered: