Complete Guide to Angular 2 forms

In This Tutorial you will learn how To deal with Forms in Angular 2 . Before starting this tutorial I will recommend you to read beginners guide to Angular 2 .Basically A form creates a cohesive, effective, and compelling data entry experience. An Angular form coordinates a set of data-bound user controls, tracks changes, validates input, and presents errors.

I am sure We’ve all used a form to login, submit a help request, place an order, book a flight, schedule a meeting and perform countless other data entry tasks. Forms are the mainstay of business applications.

Angular 2 offers two ways to develop forms with validation:

  • A template-driven approach: Provides a declarative API where we declare the validations into the template of the component.
  • A model-driven approach: Provides an imperative API with FormBuilder.

So lets get started learning angular 2 forms .Forms are essential for each CRUD (Create Retrieve Update and Delete) application. In our case, we want to build a form for entering the details of the developers we want to store.

if want to create a ecommerce site using angularjs then this tutorial is for you :)

Add the following imports to add_developer.ts: ( dunebook/ts/formstut/add_developer.ts.)

import {
  FORM_DIRECTIVES,
  FORM_PROVIDERS
} from 'angular2/common

The next thing we need to do is add FORM_DIRECTIVES to the list of directives used by the AddDeveloper component. The FORM_DIRECTIVES directives contains a set of predefined directives for managing Angular 2 forms, such as the form and ngModel directives.

The FORM_PROVIDERS is an array with a predefined set of providers that we can use for injecting the values associated with their tokens in the classes of our application.

Now update the AddDeveloper implementation to the following:

@Component({
  selector: 'dev-add',
  templateUrl: './add_developer.html',
  styles: […],
  directives: [FORM_DIRECTIVES],
  providers: [FORM_PROVIDERS]
})
export class AddDeveloper {
  developer = new Developer();
  errorMessage: string;
  successMessage: string;
  submitted = false;
  technologies: string[] = [
    'JavaScript',
    'C',
    'C#',
    'Clojure'
  ];
  constructor(private developers: DeveloperCollection) {}
  addDeveloper() {}
}

The developer property contains the information associated with the current developer that we’re adding with the form. The last two properties, errorMessage and successMessage, are going to be used respectively for displaying the current form’s error or success messages once the developer has been successfully added to the developers collection, or an error has occurred.

Digging into the template-driven form’s markup

As the next step, let’s create the template of the AddDeveloper component (step-1/add_developer.html). Add the following content to the file:

<span *ngIf="errorMessage"
       class="alert alert-danger">{{errorMessage}}</span>
<span *ngIf="successMessage"
       class="alert alert-success">{{successMessage}}</span>

These two elements are intended to display the error and success messages when adding a new developer. They are going to be visible when errorMessage and successMessage respectively have non-falsy values (that is, something different from the empty string, false, undefined, 0, NaN, or null).

Now let’s develop the actual form:

<form #f="ngForm" (ngSubmit)="addDeveloper()"
      class="form col-md-4" [hidden]="submitted">
  <div class="form-group">
    <label class="control-label"
           for="realNameInput">Real name</label>
    <div>
      <input id="realNameInput" class="form-control"
             type="text" ngControl="realName" required
             [(ngModel)]="developer.realName">
    </div>
  </div>
  <button class="btn btn-default"
          type="submit" [disabled]="!f.form.valid">Add</button>
  <!-- MORE CODE TO BE ADDED -->
</form>

We declare a new form using the HTML form tag. Once Angular 2 finds such tags in a template with an included form directive in the parent component, it will automatically enhance its functionality in order to be used as an Angular form. Once the form is processed by Angular, we can apply form validation and data-bindings. After this, using #f="ngForm", we will define a local variable for the template called f, which allows us to reference to the current form. The last thing left from the form element is the submit event handler. We use a syntax that we’re already familiar with (ngSubmit)="expr", where in this case, the value of the expression is the call of the addDeveloper method attached to the component’s controller.

Now, let’s take a look at the div element with class name control-group.

Note

Note that this is not an Angular-specific class; it is a CSS class defined by Bootstrap that we use in order to provide a better look and feel to the form.

Inside of it, we can find a label element that doesn’t have any Angular-specific markup and an input element that allows us to set the real name of the current developer. We set the control to be of a type text and declare its identifier to equal realNameInput. The required attribute is defined by the HTML5 specification and is used for validation. By using it on the element, we declare that it is required for this element to have a value. Although this attribute is not Angular-specific using the ngControl attribute, Angular will extend the semantics of the required attribute by including validation behavior. This behavior includes setting specific CSS classes on the control when its status changes and managing its state that the framework keeps internally.

The ngControl directive is a selector of the NgControlName directive. It enhances the behavior of the form controls by running validation over them for the change of their values, and applying specific classes during the controls’ life cycle. You might be familiar with this from AngularJS 1.x where the form controls are decorated with the ng-pristine, ng-invalid, and ng-valid classes, and so on, in specific phases of their lifecycle.

The following table summarizes the CSS classes that the framework adds to the form controls during their lifecycle:

Classes Description
ng-untouched The control hasn’t been visited
ng-touched The control has been visited
ng-pristine The control’s value hasn’t been changed
ng-dirty The control’s value has been changed
ng-valid All the validators attached to the control have returned true
ng-invalid Any of the validators attached to the control has a false value

According to this table, we can define that we want all the input controls with invalid value to have a red border in the following way:

input.ng-dirty.ng-invalid {
  border: 1px solid red;
}

The exact semantics behind the preceding CSS in the context of Angular 2 is to use a red border for all the input elements whose values have been changed and are invalid according to the validators attached to them.

Now, let’s explore how we can attach different validation behavior to our controls.

Using the built-in form validators

We already saw that we can alter validation behavior to any control by using the required attribute. Angular 2 provides two more built-in validators, as follows:

  • minlength: Allows us to specify the minimum length of the value that a given control should have.
  • maxlength: Allows us to specify the maximum length of the value that a given control should have.

These validators are defined with Angular 2 directives and can be used in the following way:

<input id="realNameInput" class="form-control"
       type="text" ngControl="realName"
       minlength="2"
       maxlength="30">

This way, we specify that we want the value of the input to be between 2 and 30 characters

Defining custom control validators

Another data property defined in the Developer class is the email field. Let’s add an input field for this property. Above the button in the preceding form, add the following markup:

<div class="form-group">
  <label class="control-label" for="emailInput">Email</label>
  <div>
    <input id="emailInput"
           class="form-control"
           type="text" ngControl="email"
     [(ngModel)]="developer.email"/>
  </div>
</div>

We can think of the [(ngModel)] attribute as an alternative to the ng-model directive from AngularJS 1.x. We will explain it in detail in the Two-way data binding with Angular 2 section.

 

Although Angular 2 provides a set of predefined validators, they are not enough for all the various formats our data can live in. Sometimes, we’ll need custom validation logic for our application-specific data. For instance, in this case, we want to define an e-mail validator. A typical regular expression, which works in general cases (but does not cover the entire specification that defines the format of the e-mail addresses), looks as follows: /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/.

In ch6/ts/step-1/add_developer.ts, define a function that accepts an instance of Angular 2 control as an argument and returns null if the control’s value is empty or matches the regular expression mentioned earlier, and { 'invalidEmail': true } otherwise:

function validateEmail(emailControl) {
  if (!emailControl.value ||
    /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/.test(emailControl.value)) {
    return null;
  } else {
    return { 'invalidEmail': true };
  }
}

Now, from the modules angular2/common and angular2/core import NG_VALIDATORS and Directive, and wrap this validation function within the following directive:

@Directive({
  selector: '[email-input]',
  providers: [provide(NG_VALIDATORS, {
    useValue: validateEmail, multi: true
  })]
})
class EmailValidator {}

In the preceding code, we defined a single multiprovider for the token NG_VALIDATORS. Once we inject the value associated with this token, we’ll get an array with all the validators attached to the given control

The only two steps left in order to make our custom validation work are to first add the email-input attribute to the e-mail control:

<input id="emailInput"
   class="form-control"
   email-input
   type="text" ngControl="email"
   [(ngModel)]="developer.email"/>

Next, to add the directive to the list used by the component AddDeveloper directives:

@Component({
  selector: 'dev-add',
  templateUrl: './add_developer.html',
  styles: [`
    input.ng-touched.ng-invalid {
      border: 1px solid red;
    }
  `],
  directives: [FORM_DIRECTIVES, EmailValidator],
  providers: [FORM_PROVIDERS]
})
class AddDeveloper {…}

Using select inputs with Angular

As the next step, we should allow the user of the application to enter the technology into which the input developer has the most proficiency. We can define a list of technologies and show them in the form as a select input.

In the AddDeveloper class, add the technologies property:

class AddDeveloper {
  …
  technologies: string[] = [
    'JavaScript',
    'C',
    'C#',
    'Clojure'
  ];
  …
}

Now in the template, just above the submit button, add the following markup:

<div class="form-group">
  <label class="control-label"
         for="technologyInput">Technology</label>
  <div>
    <select class="form-control"
            ngControl="technology" required
            [(ngModel)]="developer.technology">
        <option *ngFor="#t of technologies"
                [value]="t">{{t}}</option>
    </select>
  </div>
</div>

Just like for the input elements we declared earlier, Angular 2 will add the same classes depending on the state of the select input. In order to show red border around the select element when its value is invalid, we need to alter the CSS rules:

@Component({
  …
  styles: [
    `input.ng-touched.ng-invalid,
     select.ng-touched.ng-invalid {
      border: 1px solid red;
    }`
  ],
  …
})
class AddDeveloper {…}

Right after this, we will declare the name of the control to be equal to “technology” using ngControl="technology". By using the required attribute, we will declare that the user of the application must specify the technology into which the current developer is proficient. Let’s skip the [(ngModel)] attribute for the last time and see how we can define the select element’s options.

Inside the select element, we will define the different options using:

<option *ngFor="#t of technologies"
        [value]="t">{{t}}</option>

This is a syntax we’re already familiar with. We will simply iterate over all the technologies defined within the AddDeveloper class, and for each technology, we will show an option element with a value of the technology name.

Using the NgForm directive

We already mentioned that the form directive enhances the HTML5 form’s behavior by adding some additional Angular 2-specific logic. Now, let’s take a step back and take a look at the form that surrounds the input elements:

<form #f="ngForm" (ngSubmit)="addDeveloper()"
      class="form col-md-4" [hidden]="submitted">
  …
</form>

In the preceding snippet, we defined a new identifier called f, which references to the form. We can think of the form as a composition of controls; we can access the individual controls through the form’s controls property. On top of this, the form has the touched, untouched, pristine, dirty, invalid, and valid properties, which depend on the individual controls defined within the form. For example, if none of the controls within the form has been touched, then the form itself is going to be with the status untouched. However, if any of the controls in the form has been touched at least once, the form will be with the status touched as well. Similarly the form will be valid only if all its controls are valid.

In order to illustrate the usage of the form element, let’s define a component with the selector control-errors, which shows the current errors for a given control. We can use it in the following way:

<label class="control-label" for="realNameInput">Real name</label>
<div>
  <input id="realNameInput" class="form-control" type="text"
     ngControl="realName" [(ngModel)]="developer.realName"
         required maxlength="50">
  <control-errors control="realName"
    [errors]="{
      'required': 'Real name is required',
      'maxlength': 'The maximum length of the real name is 50 characters'
      }"
   />
</div>

Notice that we’ve also added the maxlength validator to the realName control.

The control-errors element has the following properties:

  • control: Declares the name of the control we want to show errors for.
  • errors: Creates a mapping between control error and an error message.

Now add the following imports in add_developer.ts:

import {NgControl, NgForm} from 'angular2/common';
import {Host} from 'angular2/core';

In these imports, the NgControl class is the abstract class that represents the individual form components, NgForm represents the Angular forms, and Host is a parameter decorator related to the dependency injection mechanism

Here is a part of the component’s definition:

@Component({
  template: '<div>{{currentError}}</div>',
  selector: 'control-errors',
  inputs: ['control', 'errors']
})
class ControlErrors {
  errors: Object;
  control: string;
  constructor(@Host() private formDir: NgForm) {}
  get currentError() {…}
}

The ControlErrors component defines two inputs: control—the name of the control declared with the ngControl directive (the value of the ngControl attribute)—and errors—the mapping between an error and an error message. They can be specified respectively by the control and the errors attributes of the control-errors element.

For instance, if we have control:

<input type="text" ngControl="foobar" required />

We can declare its associated control-errors component by using the following:

<control-errors control="foobar"
      [errors]="{
       'required': 'The value of foobar is required'
      }"></control-errors>

Inside of the currentError getter, in the preceding snippet, we need to do the following two things:

  • Find a reference to the component declared with the control attribute.
  • Return the error message associated with any of the errors that make the current control invalid.

Here is a snippet that implements this behavior:

@Component(…)
class ControlErrors {
  …
  get currentError() {
    let control = this.formDir.controls[this.control];
    let errorsMessages = [];
    if (control && control.touched) {
      errorsMessages = Object.keys(this.errors)
        .map(k => control.hasError(k) ? this.errors[k] : null)
        .filter(error => !!error);
    }
    return errorsMessages.pop();
  }
}

In the first line of the implementation of currentError, we get the target control by using the controls property of the injected form. It is of the type {[key: string]: AbstractControl}, where the key is the name of the control we’ve declared with the ngControl directive. Once we have a reference to the instance of the target control, we can check whether its status is touched (that is, whether it has been focused), and if it is, we can loop over all the errors within the errors property of the instance of ControlError. The map function will return an array with either an error message or a null value. The only thing left is to filter all the null values and get only the error messages. Once we get the error messages for each error, we will return the last one by popping it from the errorMessages array.



The hasError method of every control accepts as an argument an error message identifier, which is defined by the validator. For instance, in the preceding example where we defined the custom e-mail validator, we will return the following object literal when the input control has an invalid value: { 'invalidEmail': true }. If we apply the ControlErrors component to the e-mail control, its declaration should look as follows:

  <control-errors control="email"
    [errors]="{ 'invalidEmail': 'Invalid email address' }"/>

 

Deven Rathore

Deven is an Entrepreneur, and Full-stack developer, Constantly learning and experiencing new things. He currently runs CodeSource.io and Dunebook.com.

Published by
Deven Rathore

Recent Posts

Top 10 best Opensource Jekyll themes

In this article, We will be looking at some of the top Jekyll themes. Jekyll…

2 weeks ago

5 Easy Tips on How to Cite Sources in a Technical Article

Citing is the act of acknowledging and giving credit to the original creator of the…

3 weeks ago

Features and Advantages for PostgreSQL Developers

Data can be complicated and hard to manage. However, it doesn't need to be. There…

2 weeks ago

How To Find The Top 3 Fleet Management Software Freeware

Industry experts agree on one thing - fleet management innovation is vital for optimizing business…

3 weeks ago

Top 10 Android UI animation libraries

In this article, We will be looking at some of the top Android UI animation…

4 weeks ago

How to use and enable word wrap in Notepad++

In this article, you will learn How to use and enable word wrap in notepad++.…

4 weeks ago