Blog

- February 17, 2016

In the first part of this article, we described the basics of the form support of Angular2. In this second part, we will deal with more advanced concepts.

Improving forms

As you can see, the form we created in the last section can be improved to be more concise and efficient to write. Here we will describe some techniques that leverage the power of Angular 2 components and directives to achieve this goal.

Bootstrap-ifying the form

Some classes need to be applied to forms and elements to display them using the Bootstrap 3 library. They need to be set on every element to be able to leverage its form support. Attribute directives of Angular 2 could provide us a great support to apply these under the hood to all these elements. As a matter of fact, they aim to change the appearance or behavior of an element.

In our case, we need to add the form-control class to the <input> / <select> / <textarea> HTML tags and form-horizontal to the form one. For this purpose, just create two directives with selectors that match these tags and leverage the ElementRef and Renderer classes to add the classes.

import {Directive, ElementRef, Renderer, Input} from 'Angular 2/core';

@Directive({
    selector: 'input:not([noBootstrap]), textarea:not([noBootstrap])
})
export class BootstrapInputDirective {
  constructor(el: ElementRef, renderer: Renderer) {
    renderer.setElementClass(el.nativeElement, 'form-control', true);
  }
}

@Directive({
  selector: 'form:not([noBootstrap])'
})
export class BootstrapFormDirective {
  constructor(el: ElementRef, renderer: Renderer) {
    renderer.setElementClass(el.nativeElement, 'form-horizontal', true);
  }
}

Notice that there is no impact in the component templates that use forms.

Form component for fields

As you can see there is a lot of code duplication within the form because of the use of Bootstrap. As a matter of fact, it requires a specific structure and dedicated CSS classes. In order to simplify this code, a good idea would be to modularize all this stuff into a component.

That said, some parts like the definition of inputs remain specific. Of course, we want to handle field validation within the component. Let’s first define the structure of this component. We need to provide the value of the label and the field state. We simply move the HTML block for a field into the component template.

@Component({
  selector: 'field',
  template: `
    <div class="form-group form-group-sm" [ngClass]="{'has-error':state && !state.valid}">
      <label for="for" class="col-sm-3 control-label">{{label}}</label>
      <div class="col-sm-8">
        <!-- Input, textarea or select -->
        <span *ngIf="state && !state.valid" class="help-block text-danger">
          <span *ngIf="state.errors.required">The field is required</span>
        </span>
      </div>
    </div>
  `
})
export class FormFieldComponent {
  @Input()
  label: string;

  @Input()
  state: Control;
}

As you can see, the expressions regarding field validation remain the same but do not rely on the state attribute provided to the component.

We use the template attribute here to see both template and component in a single snippet. You should externalize the HTML code into a file referenced using the templateUrl attribute.

The last thing to implement is to include the specific part, the form element itself. Angular 2 provides the ng-content component for this purpose. It allows you to put into the component template the HTML content provided when using the component. The template will now look like this:

<div class="form-group form-group-sm" [ngClass]="{'has-error':state && !state.valid}">
  <label for="for"
      class="col-sm-3 control-label”>{{label}}</label>
  <div class="col-sm-8">
    <ng-content ></ng-content>
    <span *ngIf="state && !state.valid" class="help-block text-danger">
      <span *ngIf="state.errors.required">The field is required</span>
    </span>
  </div>
</div>

We are now ready to refactor our form using this component. That way, our input automatically takes part of a Bootstrap-based form and errors are automatically displayed according to specified validations.

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

You can see that there is some duplication here at the level of the state attribute. We need to specify it on both field component and form elements. A good approach would be to let the field component deduce the controller of the element it wraps. Angular 2 provides an elegant way to do this thanks to the ContentChild decorator. You can reference components and directives within the ng-content block.

In our case, we need to find out a directive of kind NgFormControl. To achieve this, simply add a property in the component decorated with @ContentChild, as described below. There is no need now to keep the state input.

export class FormFieldComponent {
  @Input()
  label: string;

  @Input()
  feedback: boolean;

  @ContentChild(NgFormControl) state;

  (...)
}

We can now refactor our form to remove the state parameter when using the field element:

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

For our use case, we also need to implement an additional component to manage labels of the company.

Specific form component

In our company form, we want to be able to specify labels to identify the company sectors, like “IT”, “computer”, “maintenance”, “software”. Implementing a complex field will fit our needs and modularize its processing in a single entity.

We want to leverage the two way binding support of Angular 2 at this level to let the labels field of the company to be automatically updated when a label is added or removed.

Let’s create the structure of our component to make it accept a values attribute and fire a valuesChange event. With such names, the values field can leverage the [(values)] expression to use two way binding.

@Component({ 			
  selector: labels', 			
  template: ` 			
    (...)
  ` 			
}) 			
export class LabelsComponent { 			
  @Input() 			
  values:string[]; 			

  @Output() 			
  valuesChange: EventEmitter; 			

  constructor() {
    this.valuesChange = new EventEmitter(); 			
  } 			

  (...)
}

We can notice the use of the @Input annotation for parameters and @Ouput for custom events of the component.

The component builds a list of Bootstrap labels using the values attribute. For each element, we attach an event to delete the label from the list. Besides this list, a dedicated input is added for the label creation. Here is a simple version of the component template.

@Component({
  selector: 'labels',
  template: `
    <div *ngIf="values">
      <span *ngFor="#value of values" style="font-size:14px"
          class="label label-default" (click)="removeValue(tag)">
        {{value}} <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
      </span>
      <span> | </span>
      <span style="display:inline-block;">
        <input [(ngModel)]="valueToAdd" style="width: 50px; font-size: 14px;" class="custom"/>
        <em class="glyphicon glyphicon-ok" aria-hidden="true" (click)="addValue(valueToAdd)"></em>
      </span>
    </div>
  `
})

All these events are attached to internal removeValue and addValue methods of the component. Each time the labels are updated the valuesChange event must be fired to update external bindings.

export class LabelsComponent implements OnInit {
  (...)

  removeValue(value:string) {
    var index = this.values.indexOf(label, 0);
    if (index != undefined) {
      this.values.splice(index, 1);
      this.valuesChange.emit(this.values);
    }
  }

  addValue(value:string) {
    this.values.push(this.valueToAdd);
    this.valuesChange.emit(this.values);
    this.valueToAdd = '';
  }
}

This component can be simply referenced within the form component and used in its associated template.

<form [ngFormModel]="companyForm">
  (...)
  <field label="Tags">
    <labels [(values)]="company.tags"></labels>
  </field>
  (...)
</form>

This approach provides an interesting way to implement a custom form component but its main drawback is that this component doesn’t take part of the form controller. Angular 2 makes it possible to improve this component by leveraging the ngModel feature.

NgModel-compatible component

The labels component we implemented is fine since it allows you to bring some advanced form elements. Its main drawback is that it is not taken into account in the form validation. What happens if I want to ensure that the list of tags isn’t empty?

We previously discussed that Angular 2 provides two way binding and validation for native form elements like inputs, selects and textarea. Angular 2 is extendable and allows you to bring this feature into custom components. That way such components can take part in the validation of the form. This means that if the validation fails for the component, the whole form will be invalid.

Let’s start to adapt the custom component implemented in the previous section. At this level, the only thing to do is to the input property labels since corresponding value will now be provided by ngModel itself. A new method must be added to allow you to set this value directly.

export class LabelsComponent {
  (...)

  writeLabelsValue(labels:string[]) {
    this.labels = labels;
  }

  (...)
}
NgModel

The value accessor corresponds to a directive that will be attached to the custom component itself. This way we will be able to detect the labelsChange event and trigger the registered onChange callback. For information the writeValue method is called when the ngModel is updated programmatically. This makes it possible to call the host (i.e. the component attached on) to set this new value.

@Directive({
  selector: 'labels',
  host: {'(labelsChange)': 'onChange($event)'}
})
export class LabelsValueAccessor implements ControlValueAccessor {
  onChange = (_) => {};
  onTouched = () => {};

  constructor(private host: LabelsComponent) {

  }

  writeValue(value: any): void {
    this.host.writeLabelsValue(value);
  }

  registerOnChange(fn: (_: any) => void): void { this.onChange = fn; }
  registerOnTouched(fn: () => void): void { this.onTouched = fn; }
}

The remaining task involves registering this class within the providers of the component to make the class take part in the ngModel processing.

const CUSTOM_VALUE_ACCESSOR = new Provider(
    NG_VALUE_ACCESSOR, {useExisting: forwardRef(() => LabelsValueAccessor), multi: true});

@Directive({
  (...)
  providers: [CUSTOM_VALUE_ACCESSOR]
})
export class LabelsValueAccessor implements ControlValueAccessor {
  (...)
}

We can now use both ngModel and ngFormControl directives on our custom component.

<field label="Tags">
  <labels [ngFormControl]="companyForm.controls.tags"
               [(ngModel)]="company.tags"></labels>
</field>

Asynchronous validation for fields

We previously saw a simple custom validator to validate zip codes. Validations can be a bit more advanced and require an exchange with the server. Imagine we want to ensure that the name of the company is unique. For this, we will trigger a HTTP request that looks for a company with the same name. If an empty result is received, we know that the name is unique.

In this case, we need to exclude the current company from the check, otherwise it would appear in the results.

We will leverage the CompaniesService. So the first thing is to implement the request.

findCompanyByName(companyName:string) {
  var headers = new Headers();
  this.createAuthorizationHeader(headers);

  return this.http.get(
    `https://Angular 2.apispark.net/v1/companies/?name=${companyName}`, {
      headers: headers
  }).map(res => res.json());
}

Now we need to be able to reference the service from the validation function. There is no dependency injection at this level but we can leverage closures. As a matter of fact, we have access to the service instance from the component where we register validators. So we will implement a function that will accept the service as parameter and create the actual validation function. This function will have access to the service when called during the validation process.

export function createUniqueNameValidator(service:CompaniesService) {
  return function(control) {
    (...)
  }
}

We can now implement the asynchronous validation function. Such a function leverages promises to notify Angular 2 of the success or failure of the validation. For that reason, the validation function must return this promise. In all cases, the promise must be resolved: with null in the first case and with an object containing the key of the validator in the other case. The following code describes the whole code of this validator.

export function createUniqueNameValidator(
	      service:CompaniesService,component:DetailsComponent) {
  return function(control) {
    return new Promise((resolve, reject) => {
      service.findCompanyByName(control.value).subscribe(
        data => {
          if (data.length === 0 || (data.length === 1 &&
                component.company.id === data[0].id)) {
            resolve(null);
          } else {
            resolve({uniqueName: true});
          }
        },
        err => {
          resolve({uniqueName: true});
        }
      });
    });
  };
}

We could stop here but we want to offer a great user experience. So we need to improve our form a little bit to display a hint about the asynchronous validation. For this we can improve our field component.

Let’s start by adding a new feedback property to specify the component required to use Bootstrap feedback for the input. We also add three methods to detect the status of the feedback:

  • isFeedbackValid: the asynchronous validation is successful
  • isFeedbackNotValid: the asynchronous validation failed
  • isFeedbackPending: the asynchronous validation is in progress

These methods leverage the control associated with the component by checking the valid and pending properties.

@Component({
  (...)
})
export class FormFieldComponent {
  (...)

  @Input()
  feedback: boolean;

  (...)
  isFeedbackValid() {
    return this.state && this.feedback &&
       !this.state.control.pending && this.state.valid;
  }

  isFeedbackNotValid() {
    return this.state && this.feedback &&
       !this.state.control.pending && !this.state.valid;
  }

  isFeedbackPending() {
    return this.state && this.feedback && this.state.control.pending;
  }
}

The component template can now be updated to take these states into account to display the correct icons within the associated input element.

@Component({
  selector: 'field',
  template: `
    <div
         [ngClass]="{ (...), 'has-feedback':feedback}">
      <label *ngIf="label" for="for"
         class="col-sm-2 col-md-2 control-label">{{label}}</label>

      <div class="col-sm-8 col-md-8"
           [ngClass]="{'col-sm-8': label, 'col-md-8': label}">
        <ng-content ></ng-content>
        <span *ngIf="isFeedbackValid()" 
              class="glyphicon glyphicon-ok form-control-feedback text-success"
              aria-hidden="true"></span>
        <span *ngIf="isFeedbackNotValid()"
              class="glyphicon glyphicon-remove form-control-feedback"
              aria-hidden="true"></span>
        <span *ngIf="isFeedbackPending()"
              class="glyphicon glyphicon-refresh glyphicon-refresh-animate text-muted form-control-feedback"
              aria-hidden="true"></span>
        (...)
      </div>
    </div>
  `,
  styles: [
    `.glyphicon-refresh-animate {
      -animation: spin .7s infinite linear;
      -webkit-animation: spin2 .7s infinite linear;
    }`,
    `@-webkit-keyframes spin2 {
      from { -webkit-transform: rotate(0deg);}
      to { -webkit-transform: rotate(360deg);}
    }`
  ]
})
export class FormFieldComponent {
  (...)
}

We also defined some CSS styles for the component to have an spinning icon when the validation is in progress. Here is the result:

validation-async-successful

validation-async-pending

validation-async-failure

That’s all for this second part. In the next and last part, we will focus on the form submission.

CTA_free trial_3

  • Pingback: Implementing Angular2 forms – Beyond basics (part 1) | Restlet - We Know About APIs()

  • Sean

    Great stuff… any github repo for source?

    Regards,

    Sean

  • Pingback: This week in API land #41 | Restlet - We Know About APIs()

  • Ankit Singh

    Just one thing that I think is missing, is debouncing server requests ? Would be very helpful.

  • The Renderer API has changed and can no longer cope with ElementRefs, hence

    renderer.setElementClass(el, ‘form-control’, true);

    in BootstrapInputDirective needs to become

    renderer.setElementClass(el.nativeElement, ‘form-control’, true);

    • Thanks very much, Christian, for pointing this out! I updated the blog post and the sources accordingly…

      Thierry

  • Great article, thank you! We made a custom field component pretty much based on your `FormFieldComponent`.

    One issue we have, not sure if related to the custom component (or an out-of-the-box Angular2 behaviour) is related to `form.touched` property. When one of the input gets touched and its control.touched becomes true, the parent form.touched stays still false. The same e.g. does not hold for dirtyness: form.dirty is true when an inner control.dirty becomes true.

    By any chance, did you experience anything like this? Thank you

  • Hello Thierry,

    Awesome post! Do you have any suggestions on how to structure an app with a large form which can have several “steps”, with each step adjusting according to the data being entered in the previous step? Any open source project that you may know of?

    I was thinking of having a high level FromBuilder object which could be shared amongst the child components (for each step), or a service which each component could update as the user moved through the steps… not fully sure what could be a good design for this.

    Thanks!

  • Pingback: Implementing Angular2 forms – Beyond basics (part 3) | Restlet - We Know About APIs()

  • Thanks for this post, your custom “ngmodel-compatible” component is really helpful!

    But do you have any example of the same component with a model-driven form approach to use like this ?

    constructor() {
    this.tagsControl = new Control([‘tag1’, ‘tag2’, ‘tag3’], Validators.required);

    this.companyForm= this.formBuilder.group({
    ‘tags’: this.tagsControl,

    });
    }

    • Pleased to hear that 😉

      I think that you should use ngFormControl in this case:

      <labels [ngFormControl]="tagsControl"></labels>

  • Oups, html has been removed, here it is:

    {form [ngFormModel]=”companyForm”}
    {labels ngControl=”tagsControl”}{/labels}

    {/form}

  • Is it best practice to use ngFormControl and ngModel together?

    Thanks

    • I think that it’s made for different issues:

      * Controls allow you to apply some validation and be notified when values are updated leveraging observables and the valueChanges attribute. With validation, you can make your form interactive to display the user if there are problems with values it provided.
      * ngModel provides two-way binding for elements bound on the form elements.

      If your form is just, for example, for filtering a list (i.e. the application doesn’t need to update it, only the user), controls are enough. If you need two-way binding you can add ngModel.

  • Daniel

    Hi Thierry,
    Great article, thank you!

    Regarding creating input field as components.
    Let me see if I got this right.

    Instead of trying to bind dynamically a control to an input, is it better to use ng-content and provide its content in the container? Like how you did in the article?

    I’m not even quite sure if I can use ngControl dynamically.

  • Christopher Murphy

    Hi, I don’t know whether I am missing something but injecting the constructor of a custom baserequestoptions class was working fine for me in Beta 17 but after moving to RC1 this approach doesn’t seem to work any more. I have created a plunkr to illustrate that the webapibaseurl now comes through as undefined:

    https://embed.plnkr.co/usOljRDLap9RlLd3RIBd/

    Any ideas?

  • juwil

    hi, in the FormComponent you have

    label for=”for”

    in the code. This will not be resolved. This way in a actual form you end up with lots of labels referencing an input field with the id “for”. How can I make sth. like

    label for=”{{forID}}” with a customizable value of forID?

  • Richard

    Hi, Thierry.

    You labels control seems to not be responsive to validator. Like, I set “required”, but didn’t work.

  • Pingback: Angular 2 Forms | Seemed Worthwhile At the Time()

  • Pingback: Angular 2 Components & UI related | Seemed Worthwhile At the Time()

  • Dave

    Your examples are out of date with the released version of Angular 2. Could you please update your source repo?

  • I needed to call `.first()` on my returned Async validator Observable

  • I’m not sure, but shouldn’t `values` read `labels` or v.v.?
    BTW I’ve really learnt a lot from your post, thank you very much!

  • Jimbob

    Very good post Thierry, thank you! A question around the ngFormControl @ContentChild decorated property of FormFieldComponent: ngFormControl was apparently deprecated in the final Angular release in favor of formControl, however making these changes to your example causes the property to not be bound. Any ideas on how this can work?