Complete Angular2 Guide: Reactive Forms in depth Part 1

Ashish Singh
Aviabird
Published in
13 min readFeb 13, 2017

This article covers Reactive forms in Angular 2+ in depth.

Complete Angular 2 Guide series

What are Reactive forms?

Reactive forms or data driven forms are a totally new approach taken by the angular team. With Reactive forms we don’t rely heavily on template for structuring our forms rather we use the underlying API’s exposed by angular which is called the Reactive API and can be used by importing a module called ReactiveModule to create the forms. This way we can very easily get rid of the declarative nature of the template forms and also the near to impossible testability of the template forms. It also makes the logic sit in one place does not clutter the form template file which can sometimes grow insanely big at times.

In template forms we use ngModel ngSubmit and localRef to the form and we play around and structure it in the template before sending it to the method we want our form to be submitted to. As I said earlier with Reactive forms all the declaration and structuring of the forms happens in the code(JS/TS) that model is then used in templates to complement the form structure.

When to use Reactive Forms?

IMO Template forms are good for example when we are working with small forms like forms with one two fields. For eg. sign-in form, subscribe form. But when it comes to bigger forms which could grow upto more than 10 fields I think that is where the real magic of Reactive forms can be seen.

More importantly as we’ll see later how we can break down the creation/initialisation of Reactive forms into smaller chunks to make use of the powerful language that typescript is we can really take advantage of it in reducing the overall complexity and make the code more readable not to mention the great boost it can give to testability.

How to use Reactive Forms?

Let’s look at a very basic example of how we can create a Reactive form.

Let’s look at the steps to create a reactive form.

  • Import Reactive forms module
  • Define the form(formGroup) in code. Generally in a component.
  • Wire the form created in component to the form in template.

Before we dive into understanding reactive forms in depth I think it makes sense to first understand what reactive forms comprise of.

Form Control

FormControl are the basic building blocks of a reactive form or a form in general. Think of them as input box, select box, radio buttons, dropdown etc.

Under the hood it’s a class which tracks a particular form control and keeps track of its validity and values.

It has to be imported from @angular/forms

import { FormControl } from ‘@angular/forms’;myName: FormControl;this.myName = new FormControl(‘Aviabird’);

Form Group

FormGroup is a collection/group of FormControls. It also offers many api’s such as tracking validity and values of the whole formGroup.

It has to be imported from @angular/forms

import { FormGroup } from ‘@angular/forms’;myName: FormGroup;this.myForm = new FormGroup({
name: new FormControl(‘Test’)
});

Link here https://embed.plnkr.co/nZqZ2PFXApLm2lziUryP/

FormArray

Verbatim from angular docs It tracks the value and validity state of an array of FormControl instances.

However I would later show how that definition is not accurate and it should be Tracks the value and validity state of an array of FormControl/FormGroup/FormArray instances.

I created an issue on angular official repo and I am still waiting for any constructive response on that.

Update: Angular Team later accepted my doc change proposal.

I am going to give an example why I am proposing this.

Let’s start by understanding the structure of form we are going to create in this tutorial.

We want to create a trip something like a trip to Salt Lake City, Utah(I am attending ng-conf this April 👻 if you like this article and also attending the conference let’s catch up there).

A trip can span across many cities, further each city can have many places one visits. So a model for trip would have many cities and those cities can further have many places in them.

Our cities & places are going to be FormArray of FormGroups.

Since FormArray, FormGroup & FormControl all extend from AbstractControl so I guess a FormArray can consist of any of those .

Constructor for FormArray also shows how it expects AbstractControl[] which can be an array any of FormGroup, FormControl & FormArray.

Check the angular source code below which proves exactly that.

constructor(public controls: AbstractControl[], validator:    ValidatorFn = null, asyncValidator: AsyncValidatorFn = null) { 
..... // other not relevant code to this issue.
}

AbstractControl — can be skipped not part of the public API directly.

A thing to note about these FormControl, FormGroup & FormArray is that they all inherit from AbstractControl. AbstractControl is something which holds most of the API’s that are exposed by FormControl, FormGroup & FormArray like valid , value , errors, touched, untouchedetc. exposes some abstract method such as setValue, patchValue& reset which get overridden in all the 3 classes explained above.

Form

Till now we have seen the smallest fragment of a form, which is FormControl, FormGroup which is a group of FormControls, and FormArray which is a collection of any one type out of FormControl, FormGroup & FormArray.

On a higher level one can understand that a form is nothing but a collection of FormControls. So essentially a form is nothing but a FormGroup.

Let me walk you through what we’ll be building before jumping on designing it and start coding on it.

Let’s say you go on a summer trip to Hawai from there to SF and finally Oregon. What we are going to create is a snapshot of this trip in a sense that what all you visited are nothing but places. They can be restaurants, waterfalls, beaches etc. etc. A high level view of it can be thought of as trip has many cities and cities has many places which you visited.

If this is not very clear to you head over to the demo and try to create a trip you’ll get a good idea of how this works.

Update: Since I wrote this blog post our model for trip has changed we have removed cities(at least in the front end scheme of things).

Let’s try to design the nested form by understanding the model.

There are some rules which I keep in mind while designing the form structure. I think they can prove helpful while structuring a form so I am listing them here. Always refer to this rules and you’ll never face any difficulty and after a point of time you get used to it.

  • RULE 1: Whenever there is a formControl(smallest entity of a form like an input) with any other basic block like FormControl, FormGroup, FormArray. We have to nest them under a FormGroup.
  • RULE 2: Whenever you find yourself saying something like X has many Y, that is when you should know you are looking at a possible FormArray of Y inside X(where X is almost always a FormGroup).

Keeping in mind these 2 rules we should move ahead and start designing our trips form modelling.

We have a trip which has a name( FormControl), startDate( FormControl) fields that is when we should think of trip as a FormGroup as per RULE 1(many formControls together).

Trip has many cities that’s a 1:m(has many) relationship now as per RULE 2 we can think of Cities as a FormArray

For a one to many relationship we’ll be using a FormArrayof cities. Since trip is a collection of FormControls and FormArray it should become a FormGroup(as per RULE 1) .That completes our trip with many cities.

Let’s dig a little deeper and try to split open the city model. City has a name( FormControl) and has many places that’s a 1:m(has many) relationship, again as we did with cities, places should be a FormArray as per RULE 2.

As per our brainstorming city is a collection of a FormControl & and a FormArray, then it should become a FormGroup as per RULE 1.

At this point we have this structure.

Form Structure

Following the same design as above we can think of places as a FormArray of FormGroups. Where place is a FormGroup consisting of name which is a FormControl.

Let’s get our hands dirty now

Code for this is already available in this Github repo.

For the sake of simplicity

  • We are going to just check the validity of the form which means that if a form is valid it can be send a request to the backend and everything is good.
  • We are also going to use a service which returns us dummy data for trip edit feature mimicking a backend service which returns a trip to be edited.

We are going to achieve trip(multiple nesting) create and edit feature in parts.

Let’s first look at the trip create feature.

The first thing we should care about is the top level formGroup which acts a the main form.

Let’s call our form as tripForm

this.tripForm = new FormGroup({
name: new FormControl(‘’, Validators.required),
cities: cities
})

At this point we have not defined cities, looking at our previously designed nested model we can see cities should be a formArray of FormGroups. Let’s see how we can do this in code.

(<FormArray>this.tripForm.controls[‘cities’]).push(
new FormGroup({
name: new FormControl(name, Validators.required),
places: places
})
)

Note we can access the controls of a form like in the code above. Since cities is an array so we are pushing a FormGroup into it. This FormGroup represents a city so it also has FormControls in it. Name is one such field and other one is places which is going to be a collection of places(a FormGroup) so it going to be a FormArray.

Similarly, we have to define places too. There is a catch here with places since places are nested inside cities so we need to find out which city we are adding a new place to. For that we can pass in the city index and use that to find out the city to which we want to add a Place.

(<FormArray>(<FormGroup>  (<FormArray>this.tripForm.controls[‘cities’])
.controls[cityIndex]).controls[‘places’]).push(
new FormGroup({
name: new FormControl(name, Validators.required),
})
)

Like we did in the case of a city we are also adding name as a FormControl.

By now you have understood how we can access controls of a form using controls option. But I am sure those <FormArray> <FormGroup> look overwhelmingly complicated to you so let’s try to clear the air around that.

Typescript in an intelligent language and part of its intelligence lies in Intellisense. Intellisense is the ability to auto suggest methods on an object and auto suggestions which a very great feature. That way intellisense keeps checking in runtime what methods you are using on which object.

Those <FormArray> & <FormGroup> are a way to tell Intellisense about an object type.

Let’s look at how it works in the example above.

this.tripForm.controls['cities'] is an AbstractControl object by default. Now an abstract control object has no push method associated with it. Remember that the push method is defined in the FormArray class. As we know this object is a FormArray. So we need to explicitly tell typescript about it using casting. Casting is a way of telling typescript what type of an object we want it to behave like and hence <FormArray>.

See how intellisense thinks about it as AbstractControl

After we add the casting for <FormArray> we can see FormArray methods are being suggested using intellisense.

Now Intellisense behaves correctly.

Till now we have created the form which needs to be wired into the html template.

Next let’s see how can we do that.

<form class=”ui form” [formGroup]=”tripForm” (ngSubmit)=”onSubmit()”>

The very first thing we want to do is assign the form to the [FormGroup] with a property binding.

Next we want to assign the properties of the trip to an input,

<input class=”ta-main-input”
type=”text”
id=”tripName”
placeholder=”Trip name”
formControlName=”name”>

Notice formControlName is the name of the attribute of the FormGroup (tripForm). It should match the attribute name name in our case. Doing it like this tries to search a formControl in the parent FormGroup.

Now we come to the hard part here. It’s the nesting of cities inside the trip.

Let me try to state what are we trying to achieve here. We want to create a small form for the cities inside the trip.

<div *ngFor=”let city of tripForm.controls[‘cities’].controls; let i = index”>
<div class=”city-box” formGroupName=”{{i}}”>
<label for=”description”>City name</label>
<input type=”text”
placeholder=”Caliornia, SF”
formControlName=”name”>
</div>
</div>

Okay, let’s try to understand what all that code does. The very first thing is we are using <div *ngFor="let city of tripForm.controls['cities'].controls; let i = index">

we are using *ngFor to iterate over all the cities in the trip.

As you can see we are using a new directive here formGroupName. Here is link to formGroupName in angular docs. In short FormGroupName is used to connect a FormGroup to a parent FormGroup. Notice if there is not FormGroup using this directive is bound to throw an exception. You can check out the code in this file here.

This directive can only be used with a parent FormGroupDirective(selector: [FormGroup]).

It accepts the string name of the nested FormGroup you want to link, and will look for a FormGroup registered with that name in the parent FormGroup instance you passed into FormGroupDirective.

Notice in our case we are using index as formGroupName. It attaches the city with the index i to the FormGroup.

Please note there is one more way of doing this and our array assignment can also be achieved using FormArrayName you could check it out here. When I started working on this feature I missed this directive and went ahead with FormGroupName directive. I’ll update this blog if and when I change this.

Similarly, we can also work out our code for places.

<div *ngFor=”let place of city.controls[‘places’].controls; let j = index”>
<div class=”place-box” formGroupName=”{{j}}”>
<label for=”description”>Place Name</label>
<input type=”text”
placeholder=”Caliornia, SF”
formControlName=”name”>
</div>
</div>

At this point we have all the code we need to get our create form working.

Let’s recap what all we have achieved so far.

  • We know how to create a form structure i.e. tripForm.
  • We know how to create/add a city and place object in tripForm.
  • We also know how to show a city and place in the template.

At this point you could check out the demo and try playing around with the create section of the demo. For Edit section you’d be surprised how little code we have to write in order to get everything working. In fact we are going to use the same component and template for it. Yes.

First of all, if we have to code edit feature we need to setup a route for the sake of simplicity we are not going to go in detail on routing(I am leaving that for another blog post) but just the basic that the url bar would have an id field when we are editing the form. That way we know which trip is going to be edited. trip/{id}/edit is the url for editing format where id is the trip id.

Firstly, we are going to parse the url and see if it contains the id attribute. If it does we will set the editing status in the component to true. which is the first step in editing process.

After that we’ll be assigning all the values of the trips form such as trip name, cities and its attributes & places and its attributes.

Actually we’ll be making use of the previous code to either create new FormGroups, FormControls or initialise them.

We’ll again make use of the trip service to fetch any random trip and try to edit it. We’ll want to do that in the ngOnInit block.

ngOnInit() {
if (this.editingStatus) {
this.trip = this.trips.getTrip()
this.initForm(this.trip)
} else { //
this.initForm()
}
}

Now it’s time to do that real data initialisation when we are editing the trip. Main logic is as follows.

if(!trip) { // Creating a trip
this.addCity();
this.addPlace(0);
} else { // Editing a trip
trip.cities.forEach((city, cityIndex) => {
this.addCity(city);
city.places.forEach((place, placeIndex) =>{
this.addPlace(cityIndex, place);
})
})
}

Notice, I am using a nested for loop to assign values to the FormGroup and FormControls. There is also a very handy method called patchValue check it out here which could be used to set the value of the whole form in one go. Personally, I don’t rely on patchValue as its definition itself says.

PatchValue: It accepts an object with control names as keys, and will do its best to match the values to the correct controls in the group.

That means if it’s not able to set the value of the form it would not throw an error, This seems like an ambiguous thing to me so I am going to create FormGroup and FormControls as per my requirement. I just like it this way. It gives me more control, and since I don’t like things the abstract way so I am not using it. However you are free to use and as per your requirement.

You must have noticed the addCity and addPlace method by now, they are just encapsulating the code which we had written above and applying some logic for editing/creating logic.

With that we have everything we need for a complete nested create/edit reactive form. Hurray we just did it.

Main Project code: You can check out the real code from which the concept of this blog originated from Github repo and also download the source code here.

Demo Project: Or you could also check out the demo code here, download it here.

In the next blog post I am going to cover how we could make use of FormBuilder to make things more sleek and Validators in depth.

With this article I have tried to cover some concepts of Reactive forms in angular in depth. My main objective of this blogpost is to help you guys in shaping your thought process when approaching Reactive forms. If you have any suggestions or feedback please leave a comment in comments section.

I also want to thank Pankaj Rawat(worth following an awesome guy!!!) for helping me throughout this series.

AVIACOMMERCE: OPEN SOURCE E-COMMERCE

Aviacommerce is an open source e-commerce framework with an aim to simplify e-commerce ecosystem by creating and unifying smart services and providing consistent, cost-effective solution to sellers helping them sell more. visit aviacommerce.org to know more.

click the image to know more or visit https://aviacommerce.org

If you liked this article, click the 💚 below so that it reaches more people her on medium.

For more musings about programming, follow me here or on twitter so you’ll get notified when I write new posts.

--

--