Quickly Create Simple Yet Powerful Angular Forms

Share this article

A pen on a piece of paper with a checkbox

Forms are an essential part of many web applications, being the most common way to enter and edit text-based data. Front-end JavaScript frameworks such as Angular, often have their own idiomatic ways of creating and validating forms that you need to get to grips with to be productive.

Angular allows you to streamline this common task by providing two types of forms that you can create:

  • Template-driven forms – simple forms that can be made rather quickly.
  • Reactive forms – more complex forms that give you greater control over the elements in the form.

In this article, we’ll make a simple example form with each method to see how it’s done.

Prerequisites

You do not need to know all the details of how to create an Angular application to understand the framework’s usefulness when it comes to forms. However, if you want to get a better grasp of Angular, you can take a look at this SitePoint article series on building a CRUD app with Angular.

Requirements

We will use Bootstrap in this tutorial. It is not an integral part of an Angular application, but it will help us streamline our efforts even further by providing ready-made styles.

This is how you can add it to your application:

  1. Open the command prompt and navigate to the folder of your project

  2. Type npm install bootstrap@next. This will add the latest version of bootstrap to the project

  3. Edit the .angular-cli.json file and add a link to the Bootstrap CSS file

    "apps": [
    "styles": [
        "../node_modules/bootstrap/dist/css/bootstrap.css"
     ]
    ]    
    

    We will not use the Bootstrap JavaScript file in this application.

  4. Both template-driven Forms and Reactive Forms require the FormsModule. It should be added to the application in app.module:

    import { FormsModule } from '@angular/forms';
    @NgModule({
    imports: [
      BrowserModule,
      FormsModule
    ]
    })
    

With that out of the way, we can proceed with the forms themselves.

Template-Driven Forms

Let us assume you want to create a simple form as quickly as possible. For example, you need a company registration form. How can you create the form?

The first step is to create the <form> tag in your view.

<form #companyForm="ngForm">

We need to modify this tag in two ways in order to submit the form and use the information from the input fields in our component:

  • We will declare a template variable using the ngForm directive.
  • We will bind the ngSubmit event to a method we will create in our component
<form #companyForm="ngForm" (ngSubmit)="submitCompany(companyForm.form);">

We will create the submitCompany method in the component a bit later. It will be called when the form is submitted and we will pass it the data from the form via companyForm.form.

We also need a submit button, regardless of the content of the form. We will use a few Bootstrap classes to style the button. It is good practice to disable the button before all the data validation requirements are met. We can use the template variable we created for the form to achieve this. We will bind the disabled property to the valid property of the companyForm object. This way the button will be disabled if the form is not valid.

<button class="btn btn-primary" [disabled]="!companyForm.valid">Submit</button>

Let us assume our simple form will have two fields – an input field for the name of the company and a drop-down field for the company’s industry.

Creating form inputs

First, we create an input field for the name:

<input type="text" 
       class="form-control" 
       name="company-name">

Right now we have a standard input with the type, name and class attributes. What do we need to do to use the Angular approach on our input?

We need to apply the ngModel directive to it. Angular will create a control object and associate it with the field. Essentially, Angular does some of the work for you behind the scenes.

This is a good time to mention that ngModel requires the input field to have a name or the form control must be defined as standalone in ngModelOptions. This is not a problem because our form already has a name. Angular will use the name attribute to distinguish between the control objects.

In addition, we should specify a template variable for the input: #nameField in this case. Angular will set nameField to the ngModel directive that is applied to the input field. We will use this later for the input field’s validation. This variable will also allow us to perform an action based on the value of the field while we are typing in it.

Now our input looks like this:

<input type="text" 
       class="form-control" 
       name="company-name"
       ngModel
       #nameField="ngModel">

It is almost the same, but with a few key changes.

Validation

Let us assume we want the company name field to be required and to have a minimum length of 3 characters. This means we have to add the required and minlength attributes to our input:

<input type="text" 
       class="form-control" 
       name="company-name"
       ngModel
       #nameField="ngModel"
       required
       minlength="3">

Sounds simple enough, right? We will also need to display an error message if any of these two requirements are not met. Angular allows us to check the input’s value and display the appropriate error message before the form is submitted.

We can perform such a check while the user is typing in the form. First of all, it is a good idea to display an error only after the user has started to interact with the form. There is no use in displaying an error message right after we load the page. This is why we will insert all the error messages for this input inside the following div:

<div *ngIf="nameField.touched && nameField.errors"></div>

The ngIf directive allows us to show the div only when a specific condition is true. We will use the nameField template variable again here because it is associated with the input. In our case, the div will be visible only, if the input has been touched and there is a problem with it. Alright, what about the error messages themselves?

We will place another div inside the aforementioned one for each error message we want. We will create a new div for the error message and use the nameField template variable again:

<div class="alert alert-danger" 
     *ngIf="nameField.errors.required">
     The company name is required
</div>

We are using the “alert alert-danger” bootstrap classes to style the text field. The nameField variable has the property errors, which contains an object with key-value pairs for all the current errors. The ngIf directive allows us to show this error message only when the ‘required’ condition is not met. We will use the same approach for the error message about the minimum length.

<div class="alert alert-danger" 
     *ngIf="nameField.errors.minlength">
     The company name should be at least 3 characters long
</div>

This div will be visible only when the minlength requirements are not met. here we can make the error message a little bit more dynamic.

Currently, we have specified the minimum length in two locations – in the input’s attribute and the text field. We can improve this by replacing the hardcoded “3” with the requiredLength property of the minlength object like so:

<div class="alert alert-danger" 
     *ngIf="nameField.errors.minlength">
     The company name should be at least {{ nameField.errors.minlength.requiredLength }} characters long
</div>

This way the number of the minimum length in the error message will depend on the input’s minlength attribute.

Now, we will do the same thing with the dropdown field for the company ‘s industry:

<select class="form-control" 
        name="company-industry"
        ngModel
        #industryField="ngModel"
        required>

We will create a list of the options for the dropdown in the component associated with this view in order to avoid hard-coding values in the HTML.

export class ContactFormComponent implements OnInit {
  industries = [
    {id: 1, name: "Agriculture"},
    {id: 2, name: "Manufacturing"},
    {id: 3, name: "Energy"},
    {id: 4, name: "Transportation"},
    {id: 5, name: "Finance"}
  ];
}

Now we can list all the options in the view via the ngFor directive. It will create an option tag for every element in the industries array from the component.

<option *ngFor="let industry of industries" 
        [value]="industry.id">
        {{ industry.name }}
</option>  

The validation for this field is quite easy and similar to that for the company name field:

<div class="alert alert-danger" 
     *ngIf="industryField.touched && !industryField.valid">
      The industry is required
</div>

Now our form is ready for submission. Earlier we bound the ngSubmit event to a method called submitCompany; let’s go to the component and add that now:

export class ContactFormComponent implements OnInit {
  submitCompany(form){
    console.log(form.value);
    alert("The form was submitted");
    form.reset();
  }
}

The form parameter will contain all the data from the form. On the other hand, form.value will contain just an object with the values of the fields in the form.

Here I will just log the result in the console, but you can do whatever you want with it. I have added an alert with a message to inform the user the form was submitted. This is not required, but it is a good practice to show some sort of notification. form.reset() will reset the form to its initial state after submission, which means the fields will be emptied.

Alright let us see what our form should look like: https://sitepoint-editors.github.io/company-registration-form/

Reactive Forms

The other kind of form you can create is a reactive form, which allows you to explicitly create control objects for the form fields yourself. This approach is a good choice when you are building a more complex form and you want to have more control over its behavior.

Let us assume that we need to create an account registration form, which will have two fields for an email and a password. We will use Bootstrap to style this form as well.

The first step is to import the ReactiveFormsModule class in app.module because it is necessary for all reactive forms:

import { ReactiveFormsModule } from "@angular/forms";

@NgModule({
  imports: [
    ReactiveFormsModule
  ]
})

Then, we need to import the FormGroup and FormControl classes in the component for our page in order to explicitly define our control objects:

import { FormGroup, FormControl } from "@angular/forms";

Now we should create an instance of the FormGroup class and specify all the fields in our form. To put it simply, we will list key-value pairs. The keys will be the names of the fields and the values will be the form objects.

accountForm = new FormGroup({
    email: new FormControl(),
    password: new FormControl();

Next, we should create the form. We will once again need the <form> tag. We will add the FormGroup directive to it and associate the HTML form with the accountForm form group object we created in the component:

<form [formGroup]="accountForm"></form>

Next, we will create the email input field. We will apply the formControlName directive to it and set it to the corresponding key in the list of controls we created in the components, email.

<input type="text" 
       class="form-control" 
       id="email" 
       formControlName="email">

We will do the same for the password field:

<input type="text" 
       id="password" 
       class="form-control"
       formControlName="password">

Validation

The next step is to add validation to the form. We will not use any HTML attributes like “required” as with the template-driven forms. Instead, we have to assign all the validators when we create the form control objects.

We will go back to the component where we defined our accountForm. All the validator methods for reactive forms are defined in the Validators class, which we have to import:

import { FormGroup, FormControl, Validators } from "@angular/forms";

Then we will assign the validators to the controls in our controller. The format is the following :

  form = new FormGroup({
    fieldname: new FormControl(
                   initial value, 
                   synchronous validators, 
                   asynchronous validators)
  });

Let us assume that both the email and the password fields will be required. We should also check if the email is valid. In addition, the password should contain at least one uppercase letter, one lowercase letter, and one number. Thus, we will use the required and pattern validators from the Validators class for both fields. We will leave their initial values as an empty string.

form = new FormGroup({
    email: new FormControl("", 
           [Validators.required, 
            Validators.pattern('[a-zA-z0-9_\.]+@[a-zA-Z]+\.[a-zA-Z]+')]),
    password: new FormControl("", 
              [Validators.required, 
               Validators.pattern('^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z]).{8,}$')])
  });

Now we need to go to the template and add the validation messages. We will do this in the same way we did it with with the template-driven forms. However, we will access the control objects in a different way. In our component we can define a property that gives us access to the control in the form like so:

get email(){
    return this.accountForm.get("email");
}

We can access this property in our template. This means that instead of writing this.accountForm.get("email") every time we want to specify a validation message, we can use just email.

<div *ngIf="email.touched && email.errors">
    <div class="alert alert-danger" *ngIf="email.errors.required">
      The email is required
    </div>
  </div>
  <div *ngIf="email.errors">
      <div class="alert alert-danger" *ngIf="email.errors.pattern">
          The email is not valid
      </div>
  </div>

This way, the message “The email is required” will appear after the user touched the form and left it empty, while the message “The email is not valid” will appear as the user is typing. We can display the validation messages for the password field in the same way.

Let us move on to submitting our reactive form. Firstly, we can disable the submit button in a similar way to the one we used with the template-driven form:

<button class="btn btn-primary" type="submit"         
        [disabled]="!accountForm.valid">Sign up</button>

We also need to bind the ngSubmit event to a function, which will be called on submit.

<form [formGroup]="accountForm" (ngSubmit)="signup()">

Then we need to define that function in the controller:

signup(){
    console.log(this.accountForm.value);
    alert('The form was submitted');
    this.accountForm.reset();
}

For now, we will show the submitted data in the console. We will clear the form fields after we display a confirmation message.

Asynchronous validation

It will be great if we can check if the email the user is trying to submit is already in use. We can perform such a check even as the user is typing in if we use an asynchronous validator.

We will use a fake API for the purposes of this demo – JSON Placeholder. This is a useful tool for testing an application because it provides various kinds of data. For example, it can provide a list of users with emails, which we will pretend is the list of existing users for our demo application. You can send get and post requests to it just as you would with a real API.

We will create a service in our application that connects to this JSON API, and attaches an asynchronous validator to the email field. This way, we will be able to check if the email is already in use.

First, we will create the service. We can do that via the Angular CLI

ng g service server.service

Then, we have to add the service to app.module so that we can use it in the application:

import { ServerService } from "./server.service";
@NgModule({
  providers: [
    ServerService
  ],
  bootstrap: [AppComponent]
})

In our service, we need to import the Injectable, Http and Observable classes as well as the map and filter RxJS operators. Then we will specify the URL to our test API. After we get the results we will filter them to see if there is a user with an email that matches the one the user typed, which we will pass to it when we perform the request.

@Injectable()
export class ServerService {
  private url = "http://jsonplaceholder.typicode.com/users";

  constructor(private http: Http) { }

  checkUsers(email: string) {
    return this.http
      .get(this.url)
      .map(res => res.json())
      .map(users => users.filter(user => user.email === email))
      .map(users => !users.length);
  }
}

Now we have to create the validator, which will use this service to check the email. We will create a new typescript file, custom.validators.ts. This will allow us to separate our code in a more effective way and reuse the validator. There we will import the AbstractControl and ValidationErrors classes as well as the ServerService.

import { AbstractControl, ValidationErrors } from '@angular/forms';
import { ServerService } from './server.service'; 

export class Customvalidators{
    static checkDuplicateEmail(serverService: ServerService) {
        return (control: AbstractControl) => {
          return serverService.checkUsers(control.value).map(res => {
            return res ? null : { duplicateEmail: true };
          });
        };
    }
}

We create an instance of our serverService and call the checkUsers method we created in it. Custom validators are supposed to return null if everything is OK, or an object with key-value pairs that describe the error otherwise.

Now we will go to our component to apply the asynchronous validator to the email field. We will have to import the ServerService into the component as well and create an instance of it in order to perform the request to our test API.

import { ServerService } from "../server.service";

constructor(private serverService: ServerService){

}

accountForm = new FormGroup({
   email: new FormControl("", synchronous validators,    
       Customvalidators.checkDuplicateEmail(this.serverService))
});

The only thing left to do is add a validation message

<div *ngIf="email.errors">
     <div class="alert alert-danger" *ngIf="email.errors.duplicateEmail">
          The email is already in use
     </div>
</div>

Now let’s see what our form looks like. https://sitepoint-editors.github.io/account-registration-form/

Wrapping Up

As you can see, Angular allows you to do a few neat tricks with forms. Not only can you create simple forms quickly by making them template-driven, but you can also implement complex features in them if you need to.

Frequently Asked Questions (FAQs) about Angular Forms

What are the key differences between Template-driven and Reactive forms in Angular?

Template-driven forms are simpler and more suitable for basic forms. They are easy to use but less flexible compared to Reactive forms. They are fully defined in the template, hence the name. Reactive forms, on the other hand, are more robust and scalable. They are more suitable for complex forms and provide more control and predictability with their synchronous nature. Reactive forms are defined in the component class and then linked to native form controls in the template.

How can I validate user input in Angular forms?

Angular provides a set of built-in validators such as required, minlength, and maxlength. You can use these validators in both Template-driven and Reactive forms. In Template-driven forms, you can use directives in the template to validate user input. In Reactive forms, you can add validators as a second argument when you instantiate a FormControl.

How can I handle form submission in Angular?

In Angular, you can handle form submission by binding the form’s submit event to a method in your component class. This method will be called when the user submits the form. You can then use the form’s value property to get the user input.

How can I dynamically add form controls in Angular?

In Angular, you can dynamically add form controls using the FormArray class in Reactive forms. You can instantiate a FormArray with an array of FormControls or FormGroup instances, and then add or remove controls dynamically.

How can I use form groups in Angular?

Form groups are used to group related form controls. In Template-driven forms, you can use the ngModelGroup directive to create a form group. In Reactive forms, you can instantiate a FormGroup with an object that defines the child FormControls or FormGroups.

How can I use two-way data binding in Angular forms?

In Angular, you can use two-way data binding in Template-driven forms with the [(ngModel)] directive. This directive allows you to bind a form control to a property in your component class, and any changes to the form control or the property will automatically update the other.

How can I handle form control changes in Angular?

In Angular, you can handle form control changes by subscribing to the valueChanges observable of a FormControl or FormGroup. This observable will emit an event every time the value of the control changes.

How can I reset a form in Angular?

In Angular, you can reset a form by calling the reset method on a FormGroup or FormControl instance. This method will reset the value and validation status of the control.

How can I use custom validators in Angular forms?

In Angular, you can create custom validators by implementing the Validator interface. A custom validator is a function that takes a FormControl instance and returns an error object if the validation fails, or null if the validation passes.

How can I use error messages in Angular forms?

In Angular, you can display error messages by binding to the errors property of a FormControl or FormGroup. You can then use directives in the template to conditionally display error messages based on the validation status of the control.

Kaloyan KolevKaloyan Kolev
View Author

Kaloyan is a full-stack web developer from Sofia in Bulgaria. He currently works for Voksnet Ltd.

angularFormsLearn Angularnilsonj
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week