Angular reactive forms provide a model-driven way to build, manage and test your HTML forms. There is also a difference when it comes to setting up event handlers.
In this article, I’ll provide an introduction to Angular Reactive Forms. You’ll learn how to create model-driven forms using the FormBuilder, FormControl and FormGroup modules. This is a high-level explanation and will primarily focus on how to compose forms using these modules, and how reactive forms differ from template-driven forms. Now you may have found that the term “model-driven forms” is often used to describe the same topic, but “reactive forms” seems to be the terminology that Angular uses consistently, so we’ll stick with that.
Angular’s reactive forms provide a way to build and interact with your forms in a more programatic way. Now, some may get the impression that reactive forms differ from template forms in that you don’t need to create the template, but this is incorrect; you do need to create it. With reactive forms, however, you build a matching model in your component that provides programmatic access to your form. This is significant in two ways: first, your form is now testable; second, your template is less tightly coupled and you can move your business logic to your component, or better yet, to your service.
Differences between Template Driven and Reactive Forms
With template-driven forms, your validation logic lives in the template. Now this is fine if your validation is simple (e.g. “required” or “min-length”), but once your validation logic involves any level of complexity (and when is this not the case : – ), then your template becomes bloated and difficult to read. With reactive forms, your validation logic lives in your component, or it can be proxied to your service. In either case, this is a better approach. Also, with template-driven forms, the only way to check the form is an end-to-end test. Now there’s nothing wrong with E2E tests, but that should be a final layer of defense. The real hardened testing should come via your unit tests, and with reactive forms, this is exactly the approach you can take.
Using FormControl – Example # A – 1
The Change Event Using FormControl – Example # A – 2
The Template Using FormControl – Example # A – 3
VIEW THE FULL CONTENTS OF THIS HTML FILE HERE: https://gist.github.com/kevinchisholm/cfbc4005e0ce7041553f35e6b35a6314#file-home-a-html
Example A contains the component and template for a fairly basic reactive form, but let’s look at Example A-1 first. Notice that I’ve imported FormControl and FormGroup from @angular/forms. These are the ones I’ll need in order to start building our form. Line # 10 is interesting because I’ve added a registerForm property, but just notice this property type, which is FormGroup. So here I’m saying that my registerForm property will be an instance of the FormGroup class. In fact, on Line # 14 that’s just what happens: I instantiate that class: this.registerForm = new FormGroup({…}), and when instantiating the FormGroup class, I pass it an object. In this object, I’ve created seven instances of the FormControl class, and at this point, I’ve done two things: I’ve created an instance of FormGroup, and I’ve passed several instances of FormControl to that constructor. So, what this comes down to is that I have a form “group” and a bunch of form “controls.”
On Line # 24, I have another interesting thing going on, which is a subscription to an Observable: this.registerForm.valueChanges.subscribe. Note that this is a major departure from template-driven Angular forms. My form is now a source of continuous values that I can subscribe to. I’ve passed an anonymous function to the valueChanges.subscribe method, this anonymous function receives an object as its first argument, and that object is a data-driven representation of my form. Essentially, this data contains all of the values for all of the fields in my form. Notice that on Line # 28 I’ve included a console.dir() statement, which allows me to inspect that data that I receive each time my subscription to the form gets an update. Now if you look at Example A-2, you’ll see the contents of this data. Notice that all of the property names match the names of the FormControl instances that I created on Lines 15-21. In Example A-3, you see the actual form template, which is not all that interesting, but there are a couple of things to keep in mind. First, the template is very simple, which is an improvement over the template-driven approach to forms. Second, the form itself has a [formGroup] attribute which matches the registerForm property from my component. Also, each form control has a formControlName attribute that corresponds to a FormControl instance from my registerForm property (which is an instance of the FormGroup class).
Using FormBuilder – Example # B – 1
The Change Event Using FormBuilder – Example # B – 1
The Template Using FormBuilder – Example # B – 3
VIEW THE FULL CONTENTS OF THIS HTML FILE HERE: https://gist.github.com/kevinchisholm/cfbc4005e0ce7041553f35e6b35a6314#file-home-b-html
In Example B I have an alternative approach to my home component. I’ve imported the FormBuilder component, and on Line # 15, instead of instantiating the FormBuilder class, I call a static method of that class named: group(). Things look a little different when compared to the previous example of this component. I pass an object to the static group() method, but instead of providing instances of the FormControl class, I pass property names that match my actual form controls in the template. This, then, enables me to avoid the numerous instantiations of the FormControl class, which, in turn, makes for more concise / readable code.
Notice that on Line # 20, I assign to the address property another call to the static group() method. I’m effectively creating a nested form group, and there are a couple of real wins here. First, the syntax is so simple: I create properties, which are form controls, and then when I want to logically group some form controls, I make a call to the static group() method, passing it yet another configuration object. In this case, the logical grouping is the address, which makes sense. Note that the street, city and zip code fields are all part of an address.
At this point, you may be wondering why the values for some of the form group’s properties are strings, yet some are arrays. Well, this is one of the real benefits of the reactive form syntax. That is, when you provide an empty string, the form control’s initial value is blank. When you provide an array, the first array element is the initial value of the control, and the remaining elements are the control’s validators. In the case of firstname and lastname, both controls are made mandatory via the validators.required built-in validator. For street, zip and city, assigning an empty array tells Angular that the initial value is blank and there are no validators. In other words, an empty array is a valid assignment, but it does not provide much value. So, I could have simply provided an empty string.
On Line # 27, I’ve set up another subscription to the registerForm.valueChanges observable. So, when you look at Example B-1, you’ll notice that the data object that is passed to the subscription callback has an address property, which is an object. This address object has properties for street, zip and city. So, by creating a nested FormControl instance, I’ve created an object with another object as one of its properties
In Example B-3, you’ll see the template for this form. Notice the section with the class name “form-data-container,” which is for demonstration purposes. If you clone the Github repo for this project and run it locally, you’ll see that any values you enter into the form fields will appear in the “form-data-container” section of the UI. The main purpose for this is to demonstrate that the entire form can be represented by a data object, which can then be used to update the UI in some manner.
Summary
Angular Reactive Forms is a major improvement over the Angular 1.x way of doing things. Angular template-driven forms are a continuation of the approach taken in Angular 1.x. So, while the template-driven forms approach can be a bit easier when it comes to creating a simple form, that simplicity is quickly compromised if your form and / or validation logic increases beyond a certain point. The Reactive Forms approach requires a little more up-front effort, but the reward is quickly granted in the form of code that is testable, easier to read, and easier to manage.