Angular Reactive Forms Tutorial
In this blog post we will explore model driven forms using Angular’s FormBuilder, FormGroup and FormControl classes. Angular provides developers two ways to build forms, one uses it’s template syntax (which we explored in my previous post) and the other is this model-driven approach. Choosing which form building technique to use is completely up to the developer. Angular reactive forms are usually easier to test since you are working with objects and models. In my experience, if you are going to need to use a robust set of validation, then you should build Angular reactive forms.
NgExample
A quick note, this post is a part of a series of Angular posts. All of the code in this post, as well as links to the other posts in this series can be found on GitHub (links to other posts also included below). In this example I will be building a simple user admin form for creating a new user. Bootstrap 4 has been added to the project and will be used in building the Angular reactive form.
- Angular Tutorial: Getting started with the Angular CLI
- Angular Component Tutorial: Inputs, Outputs, and EventEmitters
- Angular Component Lifecycle
- Angular Router Tutorial: Setting Up Routing in Your Application
- Angular Module Tutorial: Application Structure Using Modules
- Using Bootstrap 4 with Angular
- Electron Tutorial: Getting started with Electron and Angular CLI
- Template Driven Angular Forms
Module Setup
In the previous template driven forms example, we setup our module for the Angular reactive form example. If you already setup this module then you can skip this portion. In case you missed it we created a FormsExampleModule
and added three components with the following steps. The “core” portion of the following commands reflects how this example repo has been setup, if you aren’t following along from previous examples or your project is setup differently, then “core” can be omitted.
ng g module core/forms-example
ng g component core/forms-example
ng g component core/forms-example/reactive-example
ng g component core/forms-example/template-example
Next I updated the routing inside the example application as I have previously for other examples. I won’t demonstrate those steps again here.
Our generated FormsExampleModule
is a bit like a stand alone application at this point. We are going to have to provide the module with the correct application pieces to build our template driven Angular form. What I mean by that; we are going to be using form specific directives to build our Angular form, thus we need to bring those directives into our module to use them. We do this by importing the “FormsModule” and providing it to the “imports” array of our FormsExampleModule
. After that we can get started building our template driven Angular form.
User Class
Again, if you followed the template driven post you can skip this portion because you’ve already done this. Next we are going to create our “User” class. This will act as our object model that will hold the information for our user. Create a user.ts
file somewhere in your project and then create the following user
class.
export class User { firstName: string; lastName: string; role: string; notes?: string; }
It’s a pretty standard barebones class that we can use to demonstrate binding data between objects and forms.
Creating an Angular Reactive Form with FormGroup, FormControl and FormBuilder
In Angular, a reactive form is a FormGroup that is made up of FormControls. The FormBuilder is the class that is used to create both FormGroups and FormControls. I encourage you to check out those links to see the full class definitions of all three. Below I am going to demonstrate how to build a FormGroup using FormBuilder for our user form.
Inside of the reactive-example.component.ts
file we will add the following. We will start with a basic form and add on to it as we go.
export class ReactiveExampleComponent implements OnInit { userForm: FormGroup; roles: Array<string> = [ 'Guest', 'Admin', 'Owner', 'Operator' ]; constructor(private formBuilder: FormBuilder) { this.userForm = this.formBuilder.group({ 'firstName': [''], 'lastName': [''], 'role': [''], 'notes': [''] }); } ngOnInit() { } }
You will need to import FormBuilder
and FormGroup
from @angular/forms
. What we now have done is created a userForm
of type FormGroup
with 4 FormControls
using the FormBuilder
. The roles array will be used in our template. These controls are pretty bare bones at the moment but we will enhance these down the line. Next let’s build out our template.
Binding to a Template with formControlName
Our template is going to look very similar to what we built in the template driven forms example. However, instead of using ngModel
and various form directives, we will bind our userForm
to our template with [FormGroup]
and formControlName
. Add the following html code to the reactive-forms-example.component.html
<form [formGroup]="userForm"> <div class="form-group"> <label for="firstName">First Name</label> <input class="form-control" name="firstName" id="firstName" type="text" formControlName="firstName"> </div> <div class="form-group"> <label for="lastName">Last Name</label> <input class="form-control" name="lastName" id="lastName" type="text" formControlName="lastName"> </div> <div class="form-group"> <label for="role">User Role</label> <select class="form-control" name="role" id="role" formControlName="role"> <option *ngFor="let userRole of roles" [ngValue]="userRole">{{userRole}}</option> </select> </div> <div class="form-group"> <label for="notes">Additional Notes</label> <textarea class="form-control" name="notes" id="notes" formControlName="notes" rows="6" placeholder="Add additional notes here."></textarea> </div> </form>
So what we have done is provided the <form> an instance of a FormGroup (the userForm
we initialized in the constructor). After doing that, we created input fields for each FormControl in the userForm
and we bound the input fields to the userForm
using formControlName
, setting the formControlName
to the name of our form control. Simple enough right? Now we have a form template built with data bound to our ReactiveExampleComponent
. To see the state of our form I am going to add a button that just calls a function in our component that console.logs(this.userForm)
.
This is what we get when the form is logged after initialization and before it is touched. Logging your form is a good way to see what Angular has for the state of your form and can be valuable for debugging. As you dig in, you can see the state of the form controls and their values.
Initializing Form Data and Validation
Next we will add some simple validation to the form, as well as demonstrate how to initialize the form with some data. Use the User class to initialize a user with default data. After that we use a built in Validators
class to set validation properties for each control. This is an optional section and can remain blank. You’ll notice that I have used an array here, that allows for multiple Validators
to be used on a control. There are some pretty handy validators that are built in by angular. Custom validators can be written but that is outside the scope of this post.
user: User = { firstName: 'New', lastName: 'User', role: 'Guest', notes: undefined }; constructor(private formBuilder: FormBuilder) { this.userForm = this.formBuilder.group({ 'firstName': [this.user.firstName, [Validators.required]], 'lastName': [this.user.lastName, [Validators.required]], 'role': [this.user.role, [Validators.required]], 'notes': [this.user.notes, [Validators.maxLength(45)]] }); }
It should be noted that there are a couple ways to do this, and this is just one of them. You could choose to create individual controls, but that would loose the functionality of the FormGroup. The nice this about the FormGroup is that it allows the developer to easily check if the whole form is valid. As you see in the console.log(this.userForm), the form has an invalid
property that is a boolean.
NgSubmit
Finally I just want to demonstrate how to use ngSubmit
with your Angular reactive forms. It’s pretty straight forward, you tag the form with a function you want to be called when the form is submitted. Then you just add a button of type submit somewhere in the form, usually the bottom. Looks something like this.
<form [formGroup]="userForm" (ngSubmit)="logFormValue()"> .... <button type="submit" class="btn btn-default" [disabled]="!userForm.valid">Submit</button> </form>
Using the valid
property of the userForm
we are able to prevent submitting an invalid form. Once valid and submitted, the logFormValue()
function will be called. This same functionality can be used in template driven forms.
Conclusion
Above I have demonstrated how to build an Angular Reactive Form, with some basic validation and data binding. I would encourage you to familiarize yourself with the FormGroup, FormControl, and FormBuilder
classes. There is a bunch of functionality that in those classes that will make building robust forms easier. I have added some additional functionality to my example repo, I encourage you to check it out and use it as a reference for your own projects. The repo link has links to my other posts as well, thanks for reading.
Trackbacks/Pingbacks