Blog

- February 11, 2016

In this article, we will describe how to implement a form to update data of a Web API hosted on Restlet Cloud. Following up “Implementing an Angular 2 frontend over an APISpark-hosted Web API”, today we will focus on how to update a particular element, a company, based on the REST resource that provides a PUT method to manipulate them.

angular-form-http

We intentionally keep data simple in order to avoid having an overly complicated form to implement. Here is the structure of the company element:

dhc-companies-structure

Creating forms

Forms are the primary way to update data within an HTML page. In this first section, we will see how to implement the structure of a form, add some styles to it using Bootstrap and add some behaviors with the Angular 2 support to make it interactive.

Form definition

As a reminder, we will start by describing the way to create a clean form using Twitter Bootstrap 3. Notice Material also provides a modern design for HTML applications.

You may already know the different HTML elements to create a form like <form>, <input>, <select> and <textarea>. Bootstrap 3 provides a set of CSS classes to make raw HTML forms more beautiful and handle the positioning of form elements.

Here is a sample of such a form.

<form class="form-horizontal">
  <!-- Name field -->
  <div class="form-group form-group-sm">
    <label for="name" class="col-sm-3 control-label">Name</label>
    <div class="col-sm-8">
      <input type="text" name="name"/>
    </div>
  </div>

  <!-- Submit button -->
  <div class="form-group">
    <div class="col-sm-offset-3 col-sm-8">
      <button type="submit" class="btn btn-primary">Save</button>
    </div>
  </div>
</form>

Of course, such code creates a beautiful but static form. Here is what it looks like.

form-static

We will see now how Angular 2 allows you to make your forms more interactive by applying some validation and displaying field errors if any.

Form state

As in version 1, Angular manages a state for the form and its fields. This state allows you to check if fields, field groups or the whole form is valid or not, according to the specified validations. Angular 2 provides a very flexible way to define states and inner states for forms.

The first approach is completely defined within the component template.

Template-driven forms

In this case, there is nothing to define the component code. Everything can be configured within its template leveraging the NgControlName directive. This allows you to define a control against a name for a specific field. This control can be then associated with a local variable. In this case, the value of this variable must be "ngForm". This local variable then allows you to check the validity of the field.

<input #name="ngForm" ngControl="name" [(ngModel)]="company.name"/>

If you want to attach this control into the whole form or a field group, simply create a local variable at the corresponding level. In this case, the state container will be updated according to the states of controls it contains.

<form #companyForm="ngForm">
  <input #name="ngForm" ngControl="name" [(ngModel)]="company.name"/>
</form>

Defining inline controls can be limited especially if you want to define custom validations.

Using existing controls

Angular 2 also lets you create by hand controls you want to link with on form elements. Such an approach must be considered when handling field validation in isolation. As a matter of fact, it’s not linked to the whole form. So the state of the form can be valid even if the field one isn’t.

That’s the reason why Angular provides a FormBuilder class for defining the validations globally for each field of the form. As we use a group in which we define fields, when a field is invalid, the whole group is.

constructor(builder:FormBuilder) {
  this.companyForm = builder.group({
    name: ['', Validators.required]
  });
}

The corresponding elements can then be referenced within the template using the FormModel and FormControl directives, as described below:

<form [ngFormModel]="companyForm">
  <input [ngFormControl]="companyForm.controls.name" [(ngModel)]="company.name"/>
</form>

Now that you’ve correctly configured form and field states, you can configure validation rules based on the field controllers.

Form validation

Some validators are provided out of the box by Angular 2:

  • required: the field can’t be empty
  • minLength: the length of a field must have a minimum length
  • maxLength: the length of a field can’t exceed a maximum length

As you can see, the list of validators is very short but it’s really easy with Angular 2 to implement and register a custom validator since it only consists of a function. For our company form, we need to check that the user filled a valid zip code within the address, i.e. a number with five digits. We can leverage regular expressions to check this. If the value is correct, simply return null. In the contrary, return an object containing the key of the validator with a sub object, as described below:

function zipCodeValidator(control) {
  var valid = /^\d{5}$/.test(control.value);
  return !valid ? { invalidZip: true }: null;
}

To register this validation function, you need to have created form controls by your own, as described in the second approach. If you only have this validator, just put it in the second element of the array used to configure form controls. If you have more than one validator, you need to use the compose method of the Validators class as described below:

this.companyForm = builder.group({
  name: ['', Validators.compose([ Validators.required, zipCodeValidator ])]
});

Now we have defined the validations for our fields. We can leverage the form state to display errors for fields that don’t match these rules.

Leveraging form state

Form state enables us to provide a better user experience by notifying users about invalid fields. As stated previously, we leverage Twitter Bootstrap to make a beautiful form.

If validation fails for a field, we simply need to add a has-error class to block for the field and display the error message in an area with help-block and text-danger classes, as described below.

<div class="form-group form-group-sm has-error">
  <label for="for" class="col-sm-3 control-label">Name</label>
  <div class="col-sm-8">
    <!-- input, select or textarea -->
    <span class="help-block text-danger">error</span>
  </div>
</div>

Of course, this must be dynamic by leveraging the field state into expressions. For the has-error class, simply use the ngClass and ngIf directives to disable and hide the error message.

<div class="form-group form-group-sm [ngClass]="{'has-error':!companyForm.controls.name.valid}">
  <label for="for" class="col-sm-3 control-label">Name</label>
  <div class="col-sm-8">
    <input #name="ngForm" ngControl="name" [(ngModel)]="company.name" required/>
    <span *ngIf="!companyForm.controls.name.valid" class="help-block text-danger">
      <span *ngIf="companyForm.controls.name.errors.required">The field is required</span>
    </span>
  </div>
</div>

The color of the input area with errors will now be changed to red, as shown below:

form-validation-error

Form controls also allow you to react to updates by leveraging the power of the reactive programming model. We will tackle such concepts in other upcoming articles.

As you can see, we implemented the foundations of forms by using Bootstrap 3 and applying validations. It’s a good start but we can go further by leveraging the Angular 2 component to simplify the form, remove the code duplication and apply behaviors transparently. We will describe this in the second part of the article. Stay tuned.

CTA_free trial_3