we will see how the Angular Forms API works and how it can be used to build complex forms. We will go through the following topics:
- What is Angular Forms all about
- Template Driven Forms, or the Angular 1 way
- Reactive Forms, or the new Functional Reactive way
- Updating Form Values, How To Reset a Form
- Advantages and disadvantages of both form types
- Can and should the two be used together?
- Which one to choose by default?
we will see how the Angular Forms API works and how it can be used to build complex forms. We will go through the following topics:
- What is Angular Forms all about
- Template Driven Forms, or the Angular 1 way
- Reactive Forms, or the new Functional Reactive way
- Updating Form Values, How To Reset a Form
- Advantages and disadvantages of both form types
- Can and should the two be used together?
- Which one to choose by default?
Angular Forms - what is it all about?
A large category of frontend applications are very form-intensive, especially in the case of enterprise development. Many of these applications are basically just huge forms, spanning multiple tabs and dialogs and with non-trivial validation business logic.
Every form-intensive application has to provide answers for the following problems:
- how to keep track of the global form state
- know which parts of the form are valid and which are still invalid
- properly displaying error messages to the user so that the user knows what to do to make the form valid
All of these are non-trivial tasks that are similar across applications, and as such could benefit from a framework.
The Angular framework provides us a couple of alternative strategies for handling forms: Let's start with the option that is the closest to Angular 1.
A large category of frontend applications are very form-intensive, especially in the case of enterprise development. Many of these applications are basically just huge forms, spanning multiple tabs and dialogs and with non-trivial validation business logic.
Every form-intensive application has to provide answers for the following problems:
- how to keep track of the global form state
- know which parts of the form are valid and which are still invalid
- properly displaying error messages to the user so that the user knows what to do to make the form valid
All of these are non-trivial tasks that are similar across applications, and as such could benefit from a framework.
The Angular framework provides us a couple of alternative strategies for handling forms: Let's start with the option that is the closest to Angular 1.
Angular Template Driven Forms
Angular 1 tackles forms via the famous ng-model
directive (read more about it in this post).
The instantaneous two-way data binding of ng-model
in Angular 1 is really a life-saver as it allows to transparently keep in sync a form with a view model.
Forms built with this directive can only be tested in an end to end test because this requires the presence of a DOM, but still, this mechanism is very useful and simple to understand.
Angular now provides an identical mechanism named also ngModel
, that allow us to build what is now called Template-Driven forms.
Note that NgModel
includes all of the functionality of its Angular 1 counterpart.
Angular 1 tackles forms via the famous
ng-model
directive (read more about it in this post).
The instantaneous two-way data binding of
ng-model
in Angular 1 is really a life-saver as it allows to transparently keep in sync a form with a view model.
Forms built with this directive can only be tested in an end to end test because this requires the presence of a DOM, but still, this mechanism is very useful and simple to understand.
Angular now provides an identical mechanism named also
ngModel
, that allow us to build what is now called Template-Driven forms.Note thatNgModel
includes all of the functionality of its Angular 1 counterpart.
Enabling Template Driven Forms
Unlike the case of AngularJs, ngModel
and other form-related directives are not available by default, we need to explicitly import them in our application module:
We can see here that we have enabled Template Driven Forms by adding FormsModule
to our application, and bootstrapped the application dynamically.
This is OK for development mode, but you might want to have a look at this post on @NgModule for an alternative bootstrap strategy for production.
With the initial configuration in place, let's now build our first Angular Form.
Unlike the case of AngularJs,
ngModel
and other form-related directives are not available by default, we need to explicitly import them in our application module:
We can see here that we have enabled Template Driven Forms by adding
FormsModule
to our application, and bootstrapped the application dynamically.
This is OK for development mode, but you might want to have a look at this post on @NgModule for an alternative bootstrap strategy for production.
With the initial configuration in place, let's now build our first Angular Form.
Our First Template Driven Form
Let's take a look at a form built according to the template driven way:
There is actually quite a lot going on in this simple example. What we have done here is to declare a simple form with two controls: first name and password, both of which are mandatory fields (marked with the required
attribute).
The form will trigger the controller method onSubmitTemplateBased
on submission, but the submit button is only enabled if both required fields are filled in.
But that is only a small part of what is going on here.
Let's take a look at a form built according to the template driven way:
There is actually quite a lot going on in this simple example. What we have done here is to declare a simple form with two controls: first name and password, both of which are mandatory fields (marked with the
required
attribute).
The form will trigger the controller method
onSubmitTemplateBased
on submission, but the submit button is only enabled if both required fields are filled in.
But that is only a small part of what is going on here.
NgModel Validation Functionality
Notice the use of [(ngModel)]
, this notation emphasizes that the two form controls are bi-directionally bound with a view model variable, named as simply user
.
This [(ngModel)]
syntax is known as the 'Box of Bananas' syntax :-)
More than that, when the user clicks a required field, the field is shown in red until the user types in something. Angular is actually tracking three form field states for us and applying the following CSS classes to both the form and its controls:
- touched or untouched
- valid or invalid
- pristine or dirty
These CSS state classes are very useful for styling form error states.
Angular is actually tracking the validity state of the whole form as well, using it to enable/disable the submit button. This functionality is actually common to both template-driven and reactive forms.
Notice the use of
[(ngModel)]
, this notation emphasizes that the two form controls are bi-directionally bound with a view model variable, named as simply user
.This[(ngModel)]
syntax is known as the 'Box of Bananas' syntax :-)
More than that, when the user clicks a required field, the field is shown in red until the user types in something. Angular is actually tracking three form field states for us and applying the following CSS classes to both the form and its controls:
- touched or untouched
- valid or invalid
- pristine or dirty
These CSS state classes are very useful for styling form error states.
Angular is actually tracking the validity state of the whole form as well, using it to enable/disable the submit button. This functionality is actually common to both template-driven and reactive forms.
The logic for all this must be in the controller, right?
Let's take a look at the controller associated with this view to see how all this form logic is implemented:
Not much to see here! We only have a declaration for a view model object user
, and an event handler used by ngSubmit
.
All the very useful functionality of tracking form errors and registering validators is taken care for us without any special configuration!
Let's take a look at the controller associated with this view to see how all this form logic is implemented:
Not much to see here! We only have a declaration for a view model object
user
, and an event handler used by ngSubmit
.
All the very useful functionality of tracking form errors and registering validators is taken care for us without any special configuration!
How does Angular pull this off then?
The way that this works, is that there is a set of implicitly defined form directives that are being applied to the view. Angular will automatically apply a form-level directive to the form in a transparent way, creating a FormGroup
and linking it to the form.
If by some reason you don't want this you can always disable this functionality by adding ngNoForm
as a form attribute.
Furthermore, each input will also get applied a directive that will register itself with the control group, and validators are registered if elements like required
or maxlength
are applied to the input.
The presence of [(ngModel)]
will also create a bidirectional binding between the form and the user model, so in the end there is not much more to do at the level of the controller.
This is why this is called template-driven forms, because both validation and binding are all setup in a declarative way at the level of the template.
The way that this works, is that there is a set of implicitly defined form directives that are being applied to the view. Angular will automatically apply a form-level directive to the form in a transparent way, creating a
FormGroup
and linking it to the form.
If by some reason you don't want this you can always disable this functionality by adding
ngNoForm
as a form attribute.
Furthermore, each input will also get applied a directive that will register itself with the control group, and validators are registered if elements like
required
or maxlength
are applied to the input.
The presence of
[(ngModel)]
will also create a bidirectional binding between the form and the user model, so in the end there is not much more to do at the level of the controller.
This is why this is called template-driven forms, because both validation and binding are all setup in a declarative way at the level of the template.
What if we don't need bi-directional binding, but only field initialization?
Sometimes we just want to create a form and initialize it, but not necessarily do bi-directional binding. We could want to let the user fill in the form and press submit, and only then get the latest value. We can do this by using the plain [ngModel]
syntax:
This will allow us to initialize the form by filling in the fields of the user object:
Sometimes we just want to create a form and initialize it, but not necessarily do bi-directional binding. We could want to let the user fill in the form and press submit, and only then get the latest value. We can do this by using the plain
[ngModel]
syntax:
This will allow us to initialize the form by filling in the fields of the user object:
What if we don't need field initialization, can we still get validation?
For example, creation forms don't need initial values, they only need validation. If we want to get only the validation functionality of ngModel
without neither the initialization of values or the bi-directional binding, we can do so with the following syntax:
For example, creation forms don't need initial values, they only need validation. If we want to get only the validation functionality of
ngModel
without neither the initialization of values or the bi-directional binding, we can do so with the following syntax:Advantages and Disadvantages of Template Driven Forms
In this simple example we cannot really see it, but keeping the template as the source of all form validation truth is something that can become pretty hard to read rather quickly.
As we add more and more validator tags to a field or when we start adding complex cross-field validations the readability of the form decreases, to the point where it will be harder to hand it off to a web designer.
The upside of this way of handling forms is its simplicity, and it's probably more than enough to build a large range of forms.
On the downside, the form validation logic cannot be unit tested. The only way to test this logic is to run an end to end test with a browser, for example using a headless browser like PhantomJs
.
In this simple example we cannot really see it, but keeping the template as the source of all form validation truth is something that can become pretty hard to read rather quickly.
As we add more and more validator tags to a field or when we start adding complex cross-field validations the readability of the form decreases, to the point where it will be harder to hand it off to a web designer.
The upside of this way of handling forms is its simplicity, and it's probably more than enough to build a large range of forms.
On the downside, the form validation logic cannot be unit tested. The only way to test this logic is to run an end to end test with a browser, for example using a headless browser like
PhantomJs
.Template Driven Forms from a functional programming point of view
There is nothing wrong with template driven forms, but from a programming technique point of view bi-directional binding is a solution that promotes mutability.
Each form has a state that can be updated by many different interactions and it's up to the application developer to manage that state and prevent it from getting corrupted. This can get hard to do for very large forms and can introduce a category of potential bugs.
Again it's important to realize that this only happens in very large/complex forms. Angular does provide a different alternative for managing forms, so let's go through it.
There is nothing wrong with template driven forms, but from a programming technique point of view bi-directional binding is a solution that promotes mutability.
Each form has a state that can be updated by many different interactions and it's up to the application developer to manage that state and prevent it from getting corrupted. This can get hard to do for very large forms and can introduce a category of potential bugs.
Again it's important to realize that this only happens in very large/complex forms. Angular does provide a different alternative for managing forms, so let's go through it.
Reactive Forms (or Model Driven)
A reactive form looks on the surface pretty much like a template driven form. But in order to be able to create this type of forms, we need first to import a different module into our application:
Note that here we imported ReactiveFormsModule
instead of FormsModule
. This will load the reactive forms directives instead of the template driven directives.
If we find ourselves in a situation where we would happen to need both, then we should import both modules, more on this later.
A reactive form looks on the surface pretty much like a template driven form. But in order to be able to create this type of forms, we need first to import a different module into our application:
Note that here we imported
ReactiveFormsModule
instead of FormsModule
. This will load the reactive forms directives instead of the template driven directives.
If we find ourselves in a situation where we would happen to need both, then we should import both modules, more on this later.
Our First Reactive Form
Let's take our previous example and re-write it but this time in reactive style:
There are a couple of differences here. First, there is a formGroup
directive applied to the whole form, binding it to a controller variable named form
.
Notice also that the required
validator attribute is not applied to the form controls. This means the validation logic must be somewhere in the controller, where it can be unit tested.
Let's take our previous example and re-write it but this time in reactive style:
There are a couple of differences here. First, there is a
formGroup
directive applied to the whole form, binding it to a controller variable named form
.
Notice also that the
required
validator attribute is not applied to the form controls. This means the validation logic must be somewhere in the controller, where it can be unit tested.What does the controller look like?
There is a bit more going on in the controller of a Reactive Form, let's take a look at the controller for the form above:
We can see that the form is really just a FormControl
, which keeps track of the global validity state. The controls themselves can either be instantiated individually or defined using a simplified array notation using the form builder.
In the array notation, the first element of the array is the initial value of the control, and the remaining elements are the control's validators. In this case both controls are made mandatory via the Validators.required
built-in validator.
There is a bit more going on in the controller of a Reactive Form, let's take a look at the controller for the form above:
We can see that the form is really just a
FormControl
, which keeps track of the global validity state. The controls themselves can either be instantiated individually or defined using a simplified array notation using the form builder.
In the array notation, the first element of the array is the initial value of the control, and the remaining elements are the control's validators. In this case both controls are made mandatory via the
Validators.required
built-in validator.
But what happened to ngModel
?
Note that ngModel
can still be used with reactive forms. It's just that the form value would be available in two different places: the view model and the FormGroup
, which could potentially lead to some confusion.
Due to this reason mixing ngModel
with reactive forms is best avoided.
Note that
ngModel
can still be used with reactive forms. It's just that the form value would be available in two different places: the view model and the FormGroup
, which could potentially lead to some confusion.
Due to this reason mixing
ngModel
with reactive forms is best avoided.Advantages and disadvantages of Reactive Forms
You are probably wondering what we gained here. On the surface there is already a big gain: We can now unit test the form validation logic.
We can do that just by instantiating the class, setting some values in the form controls and perform assertions against the form global valid state and the validity state of each control.
But this is just one possibility. The FormGroup
and
FormControl
classes provide an API that allows us to build UIs using a completely different programming style known as Functional Reactive Programming.
You are probably wondering what we gained here. On the surface there is already a big gain: We can now unit test the form validation logic.
We can do that just by instantiating the class, setting some values in the form controls and perform assertions against the form global valid state and the validity state of each control.
But this is just one possibility. The
FormGroup
andFormControl
classes provide an API that allows us to build UIs using a completely different programming style known as Functional Reactive Programming.Functional Reactive Programming in Angular
This deserves it's own blog post, but the main point is that the form controls and the form itself now provide an Observable-based API. You can think of observables simply as a collection of values over time.
This means that both the controls and the whole form itself can be viewed as a continuous stream of values, that can be subscribed to and processed using commonly used functional primitives.
For example, it's possible to subscribe to the Form stream of values using the Observable API like this:
What we are doing here is taking the stream of form values (that changes each time the user types in an input field), and then apply to it some commonly used functional programming operators: map
and filter
.
In fact, the form stream provides the whole range of functional operators available in Array
and many more.
In this case, we are converting the first name to uppercase using map
and taking only the valid form values using filter
. This creates a new stream of valid-onlyvalues to which we subscribe, by providing a callback that defines how the UI should react to a new valid value.
This deserves it's own blog post, but the main point is that the form controls and the form itself now provide an Observable-based API. You can think of observables simply as a collection of values over time.
This means that both the controls and the whole form itself can be viewed as a continuous stream of values, that can be subscribed to and processed using commonly used functional primitives.
For example, it's possible to subscribe to the Form stream of values using the Observable API like this:
What we are doing here is taking the stream of form values (that changes each time the user types in an input field), and then apply to it some commonly used functional programming operators:
map
and filter
.
In fact, the form stream provides the whole range of functional operators available in
Array
and many more.
In this case, we are converting the first name to uppercase using
map
and taking only the valid form values using filter
. This creates a new stream of valid-onlyvalues to which we subscribe, by providing a callback that defines how the UI should react to a new valid value.Advantages of building UIs using Functional Reactive Programming (FRP)
We are not obliged to use FRP techniques with Angular Reactive Forms. Simply using them to make the templates cleaner and allow for component unit testing is already a big plus.
But like its usual in the case of Observable-based APIs, FRP techniques can help easily implement many use cases that would otherwise be rather hard to implement such as:
- pre-save the form in the background at each valid state, or even invalid (for example storing the invalid value in a cookie for later use)
- typical desktop features like undo/redo
Have a look at this video Introduction to Functional Reactive Programming - Using the Async Pipe - Pitfalls to Avoid, part of the Angular Services and HTTP course for more on Functional Reactive Programming in Angular.
We are not obliged to use FRP techniques with Angular Reactive Forms. Simply using them to make the templates cleaner and allow for component unit testing is already a big plus.
But like its usual in the case of Observable-based APIs, FRP techniques can help easily implement many use cases that would otherwise be rather hard to implement such as:
- pre-save the form in the background at each valid state, or even invalid (for example storing the invalid value in a cookie for later use)
- typical desktop features like undo/redo
Have a look at this video Introduction to Functional Reactive Programming - Using the Async Pipe - Pitfalls to Avoid, part of the Angular Services and HTTP course for more on Functional Reactive Programming in Angular.
Updating Form Values
We now have APIs available for either updating the whole form, or just a couple of fields. For example, let's create a couple of new buttons on the reactive form above:
We can see here that there are two buttons for updating the form value, one for the partial updates and the other for full updates. This is how the corresponding component methods look like:
We can see that FormGroup
provides two API methods for updating form values:
- we have
patchValue()
which partially updates the form. This method does not need to receive values for all fields of the form, which allows for partial updates
- there is also
setValue()
, to which we are passing all the values of the form. In the case of this method, values for all form fields need to be provided, otherwise, we will get an error message saying that certain field values are missing
We might think that we could use these same APIs to reset the form by passing blank values to all fields.
That would not work as intended because the pristine and untouched statuses of the form and its fields would not get reset accordingly. But Angular Final provides an API for this use case.
We now have APIs available for either updating the whole form, or just a couple of fields. For example, let's create a couple of new buttons on the reactive form above:
We can see here that there are two buttons for updating the form value, one for the partial updates and the other for full updates. This is how the corresponding component methods look like:
We can see that
FormGroup
provides two API methods for updating form values:- we have
patchValue()
which partially updates the form. This method does not need to receive values for all fields of the form, which allows for partial updates - there is also
setValue()
, to which we are passing all the values of the form. In the case of this method, values for all form fields need to be provided, otherwise, we will get an error message saying that certain field values are missing
We might think that we could use these same APIs to reset the form by passing blank values to all fields.
That would not work as intended because the pristine and untouched statuses of the form and its fields would not get reset accordingly. But Angular Final provides an API for this use case.
How To Reset a Form
Notice on the button list above that there is a cancel button that calls a reset()
method, which does reset everything back to pristine and untouched:
Let's now see if its possible to mix both form types and if that is advisable.
Notice on the button list above that there is a cancel button that calls a
reset()
method, which does reset everything back to pristine and untouched:
Let's now see if its possible to mix both form types and if that is advisable.
Reactive vs Template Driven: can they be mixed?
Reactive and Template-Driven under the hood are implemented in the same way: there is a FormGroup
for the whole form, and one FormControl
instance per each individual control.
If by some reason we would need to, we could mix and match the two ways of building forms, for example:
- we can use
ngModel
to read the data and use FormBuilder
for the validations. we don't have to subscribe to the form or use RxJs if we don't wish to.
- We can declare a control in the controller, and then reference it in the template to obtain its validity state
But in general, it's better to choose one of the two ways of doing forms, and using it consistently throughout the application.
Reactive and Template-Driven under the hood are implemented in the same way: there is a
FormGroup
for the whole form, and one FormControl
instance per each individual control.
If by some reason we would need to, we could mix and match the two ways of building forms, for example:
- we can use
ngModel
to read the data and useFormBuilder
for the validations. we don't have to subscribe to the form or use RxJs if we don't wish to. - We can declare a control in the controller, and then reference it in the template to obtain its validity state
But in general, it's better to choose one of the two ways of doing forms, and using it consistently throughout the application.
Which form type to choose, and why?
Template Driven Forms are maybe for simple forms slightly less verbose, but the difference is not significant. Reactive Forms are actually much more powerful and have a nearly equivalent readability.
Most likely in a large-scale application, we will end up needing the functionality of reactive driven forms for implementing more advanced use cases like for example auto-save.
Template Driven Forms are maybe for simple forms slightly less verbose, but the difference is not significant. Reactive Forms are actually much more powerful and have a nearly equivalent readability.
Most likely in a large-scale application, we will end up needing the functionality of reactive driven forms for implementing more advanced use cases like for example auto-save.
Everything can be done in both form types, but some things are simpler using reactive forms
It's not this that there is functionality that cannot be implemented with template driven forms. But there is a lot of functionality especially more modern form features like for example auto-save that can be implemented in just a few lines of RxJs.
It's not this that there is functionality that cannot be implemented with template driven forms. But there is a lot of functionality especially more modern form features like for example auto-save that can be implemented in just a few lines of RxJs.
Which form type to choose?
Are you migrating an Angular 1 application into Angular? That is the ideal scenario for using Template Driven Forms.
Or are you building a new application from scratch? Reactive forms are a good default choice because more complex validation logic is actually simpler to implement using them.
For example, imagine a validation that requires to inspect two fields and compare them: for example a password field and a password confirmation field need to be identical.
With reactive forms, we just need to write a function and plug it into the FormControl
. With template driven forms, there is more to it: we need to define a directive and somehow pass it the value of the two fields.
Reactive forms seem great for example for enterprise applications with lots of complex inter-field business validation logic.
As mentioned before, we want to avoid situations where we are using both form types together, as it can get rather confusing. But it's still possible to use both forms together if by some reason we really need to.
Are you migrating an Angular 1 application into Angular? That is the ideal scenario for using Template Driven Forms.
Or are you building a new application from scratch? Reactive forms are a good default choice because more complex validation logic is actually simpler to implement using them.
For example, imagine a validation that requires to inspect two fields and compare them: for example a password field and a password confirmation field need to be identical.
With reactive forms, we just need to write a function and plug it into the
FormControl
. With template driven forms, there is more to it: we need to define a directive and somehow pass it the value of the two fields.
Reactive forms seem great for example for enterprise applications with lots of complex inter-field business validation logic.
As mentioned before, we want to avoid situations where we are using both form types together, as it can get rather confusing. But it's still possible to use both forms together if by some reason we really need to.
Angular University
Summary
Angular provides with us ways to build forms: Template Driven and Reactive.
The Template Driven approach is very familiar to Angular 1 developers and is ideal for easy migration of Angular 1 applications into Angular.
The Reactive approach removes validation logic from the template, keeping the templates clean of validation logic. But also allows for a whole different way of building UIs that we can optionally use.
This is not an exclusive choice but for a matter of consistency, it's better to choose one of the two approaches and use it everywhere in our application.
Angular provides with us ways to build forms: Template Driven and Reactive.
The Template Driven approach is very familiar to Angular 1 developers and is ideal for easy migration of Angular 1 applications into Angular.
The Reactive approach removes validation logic from the template, keeping the templates clean of validation logic. But also allows for a whole different way of building UIs that we can optionally use.
This is not an exclusive choice but for a matter of consistency, it's better to choose one of the two approaches and use it everywhere in our application.
No comments:
Post a Comment