Angular ( >=2.x)  offers 2 mechanisms for building forms :-
  • A Template Driven Approach
  • A Reactive Approach
Both techniques use the same underlying building blocks (classes) for managing the value and state of the HTML form elements namely
  • FormGroup – represents a group of HTML input elements. The HTML form itself is represented by a FormGroup.
  • FormControl – represents an individual HTML input element.
However, the degree of control that the user has over the form model (the FormGroup and FormControl instances) is much greater in the Reactive approach. In the Template Driven approach the responsibility of creating the FormGroup and FormControl instances lies with the framework( Angular) and the bulk of the code that the developer writes lies in the template. When building complex forms the lack of control over the form model can become a handicap and in such situations the Reactive Forms approach is ideal.
In this series of articles I am going to walk through building a Reactive Form step by step. The end product will be a form that looks like this.
I have created a simple demo application that you can download and run on your own computer as you follow along. This application allows a tea seller to enter and track consignments of tea. The requirements for the Consignment Entry form shown above are :-
  1. Consignment No, Date Received and Sender are mandatory
  2. The default Transportation Mode is Rail. But if the consignment arrived by  Road , then it is mandatory to enter the registration no of the vehicle used.
  3. For each item the user needs to choose the tea type and enter the number of containers and weight per container. The total item weight should be calculated based on the number of containers and weight of each container.
  4. There should be at least one item in the consignment.

First lets look at the raw HTML. This does does not contain any code or markup related to Angular. The classes applied to the elements  are Twitter Bootstrap classes. As we progress, we will be adding  Angular specific code and markup to the template.

The functionality related to Reactive Forms resides in the ReactiveFormsModule. So the first thing we do is add this module to the imports array of the module in which our component resides which is the ConsignmentModule (consignment.module.ts).

In our component class (edit-consignment.component.ts) we add code to create the form model. We add a member variable ‘consignmentForm’ of type ‘FormGroup’. This will represent the HTML form element. Then in the constructor we invoke a method createForm which creates our form model.

The FormBuilder API provides a shorthand or concise way to define our form model. As shown in the code snippet above, we need to inject the FormBuilder into our component class by passing it into the constructor. The  code in the createForm method creates a FormGroup object which contains child controls  such as id, sender, consignmentNo etc. Each of these is represented by a FormControl object which maintains the value and state of that particular control. The ‘items’ element is a special case in that it specifies an array of controls, but more on that later. For now let’s have a closer look at this line :

The first member in the array is the initial value of the form control which is shown to the user when the page loads. In this case we pass an empty string since we do not want to set an initial (or default) value. The second member in the array represents the list of validations that should be applied by Angular to this form control. In this case we specify two validations – the ‘required’ validation which is a built in validation offered by Angular and a custom ‘validDate’ validation that we have written. We will discuss custom validators  in a later article. Apart from the initial value and list of validations, Angular allows you to optionally specify a  list of async validations. However that is beyond the scope of this article.

Now let’s see how we associate the FormGroup and FormControl instances we created in our component with the HTML form elements they track. In our template file (edit-consignment.component.html) we have the following code (partial code shown here)

The Angular FormGroupDirective directive (which has ‘selector’ formGroup) allows us to associate the HTML form( the Form DOM element ) with the FormGroup instance we have created. We achieve this with the following piece of code.

The Angular FormControlName directive associates the HTML element with the nested form control object that we created in the component class. Note that in this case we pass the name of the formControl (a string) rather than the FormControl object itself. By adding the following piece of code to the text input element for consignment number we associate this HTML element with the FormControl object whose name is consignmentNo.

By doing this much we have achieved a lot. Whatever value that the user enters in the text input field will be available in the ‘value’ property of the corresponding FormControl object. The FormControl object will also track the ‘state’ of the HTML input element. ‘State’ essentially refers to 3 things :

  • Whether the user has set focus on the input element or not. This is tracked by the FormControl property ‘touched’ and its inverse ‘untouched’
  • Whether the user has modified the value of the input element or not. This is tracked by the FormControl property ‘dirty’ and its inverse ‘pristine’
  • Whether the value entered by the the user is valid or not. This is tracked by the FormControl property ‘valid’ and its inverse ‘invalid’

The value and state of the individual form controls inside a form group are aggregated by the enclosing ‘FormGroup’ object. So in your template if you added the following line , the value of all the child elements of the form element would be displayed.

Similarly the line below would display if the form element is in a ‘valid’ state or not. The form is considered valid if all its child elements have passed all validations specified for that element.

Note the markup for the submit button. The disabled property of the button DOM element is associated with the invalid property of the form group representing the form. So the submit button remains disabled until all validations pass.

So now we have defined our form model and associated our HTML input elements with our form model. What happens when the user submits our form? We would most likely want to send the data over to a server.  In order to do that we bind to the ‘ngSubmit’ event of the FormGroup which represents the form as shown below. Why do we bind to ‘ngSubmit’ rather than simply ‘submit’? Well, the FormGroup directive listens for the submit event on the form, emits the ‘ngSubmit’ event and  prevents the browser from submitting the form by returning false. This is what we want since this is a single page application and we want to handle the form submit ourselves.

In the onSubmit() method in the controller, all the values that the user entered are available in the value property of the FormGroup object. You can call you backend API to save the data. Since this is a demo app we simply log the  value to the console.

This concludes the first part of this series. In this part we have covered the following :-

  • Importing the ReactiveFormsModule and creating the form model in the component class
  • Setting initial values and adding validators to the FormControl instances
  • Associating the FormGroup and FormControl instances with corresponding DOM elements in the HTML template
  • Accessing the value and validity of the form controls.

You can continue reading Part 2 here.

Reactive Forms in Angular – A Practical Guide (Part 1)
Tagged on: