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 reactive Angular forms, covering
ngModelGroup, submit events, validation and error messages.
Table of contents
- High-level terminology
- Form base and interface
- ngModule and reactive forms
- Reactive approach
- Reactive submit
- Reactive error validation
- Simplifying with FormBuilder
- Final code
Before we begin, let’s clarify what “reactive” forms mean from a high level.
When we talk about “reactive” forms (also known as model-driven), we’ll be avoiding directives such as
If you’re yet to dive into “template-driven” forms, check out my previous post on it.
Form base and interface
The base form structure that we’ll be using to implement our reactive 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 reactive forms
Before we even dive into reactive forms, we need to tell our
@NgModule to use the
You will obviously need to wire up all your other dependencies in the correct
ReactiveFormsModulefor reactive forms, and
FormsModulefor template-driven forms.
Let’s begin with a base
SignupFormComponent and add 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 do need to start understanding
FormGroup, and finally move onto the amazing
FormControl and FormGroup
Before digging into these APIs, I would strongly recommend checking out my previous article on template-driven forms to gain a better understanding of what’s happening.
Let’s define what FormControl and FormGroup are:
- FormControl is a class that powers an individual form control, tracks the value and validation status, whilst offering a wide set of public API methods.
- FormGroup is a group of FormControl instances, also keeps track of the value and validation status for the said group, also offers public APIs.
Right, so we have an example of invoking new instances of
FormGroup, now how do we use them? It’s actually much easier than you’d think. Let’s assume we’ll bind our
FormGroup to a fresh code example before we continue with our signup form, so hopefully things click and you can follow easier:
Note: you’ll notice
name=""attributes have been toasted, this is good thing as it makes our markup less declarative (which can become complex, quickly, with forms)
That’s it! On the form, we must declare
[formGroup] as a binding, and
formControlName as a directive with the corresponding Object key name. This is what we have:
Implementing our FormGroup model
So now we’ve learned the basis of
FormControl, we can think about implementing our own now. But first, what does our interface say?
Yes, we can create nested
FormGroup collections! Let’s make that come alive, but with no initial data:
If we did want to set initial data, we can do so as per the above examples whereby we pre-populate particular strings with information, which typically are data-driven from a backend API.
Binding our FormGroup model
Now we’ve instantiated the
FormGroup model, it’s obviously time to bind it to the DOM. Using what we’ve learned before, let’s go ahead:
FormControl matches with the DOM structure:
Unlike template-driven forms, where we would do something like
#f="ngForm", and print
f.value in the DOM to check our form out, we do the opposite with reactive forms, as the
[formGroup] is a directive that we bind to, passing the public
user Object in:
This is actually the exact same as the template-driven approach, however we can optionally reference the form internally to the component, instead of passing it in as a value. First, the
Notice how we just passed
user 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 the
user reference we pass into
value is the same reference as printing
user.value out in the DOM. That’s literally it, you’re free to pass values to your backend API.
Now, for the more internal approach. Because
this.user is technically our model, we can simply reference the model
onSubmit internally, and not pass
user through as a function argument:
Reactive error validation
So far, we’ve implemented zero validation! Oh my. Let’s fix this. To add validation, we actually need to import the lovely
@angular/forms and pass them in as a second argument to our
Rule: need multiple
FormControl? Use an array to contain them.
This is now a replacement for adding
<input required> to the DOM, which means we never have to touch it. Internally, when using
required directives in template-driven forms, Angular will actually create this stuff under-the-hood for us, so that’s the main difference between the two implementations.
However, we are going to create
[disabled] binding just like in the template-driven approach to disable the submit when the form is invalid:
All ready to go, now when we actually have validation errors, we need to now show them. When it comes to referencing the controls powering the errors, 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:
?.propis called the “Safe navigation operator”
We also have a
.get() method that will lookup that control (I much prefer this as it’s a nicer API and avoids
So, onto implementing the validation, we need to add the following to the correct portions of the form:
trueonce the user has blurred the input, which may be a relevant time to show the error if they’ve not filled anything out
Code so far
This is what we’ve achieved up until now:
Simplifying with FormBuilder
This is where things get even smoother! Instead of using
FormControl directly, we can use a magical API underneath that does it all for us. Meet
First up, we’ll need to change our imports from this:
To this (with additional
constructor injection to make
this.fb available as the
This is because
user: FormGroup; on our component class is of type
FormGroup. So, what is
FormBuilder? It’s essentially syntax sugar that creates
FormArray instances for us (we’ll cover
FormArray in another article). It’s just simple sugar, but now you know what it’s for.
Let’s refactor our code to use
The refactoring is self-explanatory, but let’s roll over it quickly.
Instead of using
new FormGroup() for example, we’re injecting
fb, and creating a new
this.fb.group(). The structure of these are identical to creating the controls and groups by themselves, it’s just syntax sugar. Which leaves us with a component class that looks like this:
We’re all done for this tutorial. Keep an eye out for custom validation and more to come.
FormGroup and FormControl code
Here’s the fully working final code from what we’ve covered for
Here’s the fully working final code from what we’ve covered for