Angular2: Building nested, reactive forms
In Angular2, there are two approaches to building forms. One are template-driven forms. The other way are reactive forms. When we build rather sophisticated forms, we might want to write custom components for custom form elements and then compose several of those components into a concrete form.
In this article, we will show by example how to implement nested forms with the reactive forms API. In the official Angular2 documentation, this approach is also called “dynamic forms”. We will also try to understand the concept of a form model that backs concrete form and input elements.
To make things clear to the reader it’s a good idea to highlight the differences between template-driven forms and reactive forms first. In the second part, we will build a nested, reactive form.
Understanding the form model
In Angular2, an application holds a form model that represents input values, validation state, its set of controls, and so on. The basic building blocks of that API are
FormArray. Instances of these objects are then mapped to the DOM.
Let’s just take a simple form with the following controls:
- name: text, required
- quantity: number
A template-driven form
Template-driven forms are probably the most natural to anyone working with Angular2 forms for the first time. It starts with a HTML template, a
<form> element and two
Two directives bring this form into life:
First, the expression
#myForm="ngForm"creates an instance of the
ngFormdirective and assigns it to the template variable
Notice that we use the variable in following parts of the template. It’s a so-called template reference variable.
NgForm builds a form model by reading the HTML template.
The form is reflected by the expression
myForm.form, an instance of
Then, for each element with a
NgModeldirective, Angular creates a
If a required attribute is set on the input element, it adds the required validator to the control. The
FormControl instance is then added to the parent
FormGroup, where it can be accessed by its name. Thus, we get the following model:
FormGrouprepresenting the form
FormControlrepresenting the first input
FormControlrepresenting the second input
Broadly speaking, with template-driven forms we declare our form model by writing plain-old-fashioned HTML forms and let Angular create a form model out of the DOM.
A reactive form
Now, in reactive forms we write that form model in code and then bind instances of
FormControlinto the DOM.
In this template, we bind the member
myFormto the DOM element form by writing
The expression instantiates a
FormGroupDirectivewhich handles the binding.
Then, we bind
FormControl instances to input elements in the DOM. An example is:
Guess what? That expression creates a
FormControlDirective behind the scenes.
In the component’s code, the form model is created in the
ngOnInit()method. We also need to instantiate and add
FormBuilderlets you create those instances. A very different style of writing is to create instances with the
newkeyword. Both styles have the exact same effect — in times of dependency injection just no one is used to writing
newanymore, which is probably the reason for a builder, that, in turn, gets injected as a constructor dependency.
You can try out both forms here:
Should I stay or should I go?
What’s all the fuzz about template-driven forms and reactive forms? Should I use the former? Or the latter?
Obviously, the style of code-writing is different. In template-driven forms, we, the developers, start writing a template and let Angular create a form model. In reactive forms, we start at the other end by writing a form model and bind to that model in templates.
When we’re building nested forms, that’s an important point. With reactive forms, we can build a form model and pass that model (or parts of it) around from one component to another through Input properties. We are going to do that in the second part of this story.
Building a nested, reactive form
We are building upon or previous example and enhance the form to make it more interactive.
The user can add and remove items from the form and we will require the user to enter a total quantity sum of at least 300 items.
Notice that we put a red border around the invalid parts of the form.
So, even if you enter a name for the second item, the items of the
FormArraywill be invalid and marked in red since the sum of quantities is below the threshold (100+100 < 300).
The form will not be valid before you enter 200 and 100 in the quantity inputs (or anything else that sums up to 300 or more).
Show me the code!
Here’s the full code of that component and a plunker below. You can quickly read through that or even skip over it. We will go and refactor that into several components and turn to the details then.
Extracting an ItemFormControlComponent
First idea is obviously to extract a component that represents a single item in the form array. Let’s call that an item form control and its corresponding component is an
To make that work, we pass its index and a
FormGroupinstance as input properties. The
FormGroupis created by a simple static
We also need to keep the remove button alive, which we do by adding an output property. When a form item is removed, it will emit the index of the item. In the parent component, we wire all the things up by adding our custom
<item-control>tag to the template.
Extracting an ItemsFormArrayComponent
The second step is to extract the array of items to a component of its own.
Let’s call that the items form array which results in a component name of
Again, we pass a
FormArrayas an input property from the parent to the newly created component. Notice the slight difference to the previous approach: now, we write
formArrayName="items" on the component’s host element. The code is:
<items-array formArrayName="items" [items]="myForm.get('items')">
Of course, we can re-use
ItemFormControlComponent from the previous step. Here’s what that looks like:
A tree of components and directives
Let’s try to visualize how the tree of components looks like in our application.
Components are highlighted in tomato-red boxes. Directives are shown in white boxes.
The second line of text illustrates the DOM element which is the mount target of a particular component or directive.
Input properties are written on the edges that connects components/directives.
As you can see, instances of a form model are passed from the top-most component further down the tree.
Going on from here
Let’s just make a quick recap and summary of the things that we have looked at:
The form model API is built around
FormGroup. Directives connect an existing form model into templates (reactive forms), or build a form model from a template (template-driven forms).
A form model can be passed from one component to another using input properties. This technique allows building rather sophisticated and complex forms who are composed of several components. Reactive forms are a good fit for that idea, making it relatively easy to build nested forms.
If you want to check out the complete demo application, take a look the Plunker below. Feedback is also very welcome!