Click here to Skip to main content
15,881,803 members
Articles / Programming Languages / Typescript

Building a Custom User Interface Control Using Angular 2

Rate me:
Please Sign up or sign in to vote.
4.33/5 (2 votes)
15 Dec 2016CPOL5 min read 12.7K   148   5  
Explaining the process involved in building a basic custom user interface control using Angular 2

Sample Image - maximum width is 600 pixels

Introduction

The purpose of this article is to explain how to build a very basic custom user interface control component that can be reused in other components. I'm making the assumption that you already have knowledge of setting up an Angular 2 environment and the topic will not be covered in this article. We will write a very simple text input component that can be configured to

  • Validate that a value was entered (We will be able to switch this validation on or off)
  • Validate that the entered value was 2 characters or longer in length (We will be able to switch this validation on or off and make the minimum length configurable)
  • Provide validation messages
  • Provide a maximum length
  • Provide error and ok icon classes
  • Provide a place holder

Background

During my last few projects I realized that I rewrite the same user interface components over and over again. Most of my time is spent writing input controls with their associated validation rules rather than focusing on the important development such as the business rules of the application. Don't get me wrong, input controls and client validation is also of vital importance and something that a lot of developers overlook, but I firmly believe that this one of the areas where the 80/20 principle can be applied. Doing 20% of the work can save us 80% on the overall time spent during application development. And because these components are reusable it will have a tremendous time impact on future project timelines.

Before we start I have to mention an article that I used extensively in writing my first custom component, Connect your custom control to ngModel with Control Value Accessor , I learned most of the important concepts from this article and simply reworked this example into somthing that was applicable to my own unique projects.

Software Versions Used

  • Angular 2.1.1
  • Bootstrap 3.3.7 (Please note that Bootstrap is not required)
  • Font-Awesome 4.7.0 (Please note that Font-Awesome is not required)

Angular 2 Project Structure

I use the following structure for my Angular 2 projects

Sample Image - maximum width is 600 pixels

TextInputComponent.ts

Let's dive right in and start with the reusable text input component code. Below is the entire TypeScript file which I will explain section by section

import { Component, forwardRef, Input } from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';

const noop = () => {
};

export const textInputValueAccessor: any = {
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => TextInputComponent),
    multi: true
};

@Component({
    selector: 'TextInput',
    moduleId: module.id,
    templateUrl: './Templates/TextInput.html',
    providers: [textInputValueAccessor]
})
export class TextInputComponent implements ControlValueAccessor {
    @Input() ValidationOkFontClass: string;
    @Input() ValidationErrorFontClass: string;
    @Input() ValidationRuleRequired: boolean;
    @Input() ValidationRuleRequiredDescription: string;
    @Input() ValidationRuleMinimumLength: boolean;
    @Input() MinimumLength: number;
    @Input() ValidationRuleMinimumLengthDescription: string;
    @Input() MaximumLength: number;
    @Input() PlaceHolder: string;

    InnerValue: any = '';

    private onTouchedCallback: () => void = noop;
    private onChangeCallback: (_: any) => void = noop;

    get value(): any {
        return this.InnerValue;
    };

    set value(v: any) {
        if (v !== this.InnerValue) {
            this.InnerValue = v;
            this.onChangeCallback(v);
        }
    }

    writeValue(value: any) {
        if (value !== this.InnerValue) {
            this.InnerValue = value;
        }
    }

    registerOnChange(fn: any) {
        this.onChangeCallback = fn;
    }

    registerOnTouched(fn: any) {
        this.onTouchedCallback = fn;
    }
}

The first step is to inform NG_VALUE_ACCESSOR which component to use for data bindings. We do this by importing NG_VALUE_ACCESSOR which is part of the Angular forms package

import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';

and creating a custom value accesor that can be used by our component

export const textInputValueAccessor: any = {
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => TextInputComponent),
    multi: true
};

Next we have to set up our component decorator where we specify our textInputValueAccessor as a provider

@Component({
    selector: 'TextInput',
    moduleId: module.id,
    templateUrl: './Templates/TextInput.html',
    providers: [textInputValueAccessor]
})

Now we create our class implementing the ControlValueAccessor interface which interface exposes the functions we will need to connect ngModel to our component.

export class TextInputComponent implements ControlValueAccessor

The ControlValueAccessor interface requires the implementation of three functions

  • writeValue(obj: any) : void (Used to write a new value to the element)
  • registerOnChange(fn: any) : void (Register the function to be called when the change event occur)
  • registerOnTouched(fn: any) : void (Register the function to be called when the touch event occur)

These functions are straight forward and will not be discussed further in this article, for more information you can read the ControlValueAccessor Angular Documentation

We want to make this component as configurable as possible so that it is easy to reuse in other components or projects, so let's create the input parameters that we can configure when setting up the component

@Input() ValidationOkFontClass: string;
@Input() ValidationErrorFontClass: string;
@Input() ValidationRuleRequired: boolean;
@Input() ValidationRuleRequiredDescription: string;
@Input() ValidationRuleMinimumLength: boolean;
@Input() MinimumLength: number;
@Input() ValidationRuleMinimumLengthDescription: string;
@Input() MaximumLength: number;
@Input() PlaceHolder: string;

The input parameters will be used for

  • ValidationOkFontClass (Used to specifiy the class that will be used for the icon when validation succeeds)
  • ValidationErrorFontClass (Used to specifiy the class that will be used for the icon when validation failed)
  • ValidationRuleRequired (Used to specifiy if a value is required or not)
  • ValidationRuleRequiredDescription (Used to specifiy the message that will be displayed if a value is required)
  • ValidationRuleMinimumLength (Used to specifiy if minimum lenght validation should be applied to the value provided)
  • ValidationRuleMinimumLengthDescription (Used to specifiy the message that will be displayed if minimun lenght validation is applied)
  • MaximumLength (Used to specifiy the minimum length when minimum lenght validation is applied)
  • PlaceHolder (Used to specifiy the place holder)

Next we have an onTouchedCallback and onChangeCallback that is set to the noop function that was created as a const just after the import statements.

const noop = () => {
};

private onTouchedCallback: () => void = noop;
private onChangeCallback: (_: any) => void = noop;

I remember my confusion when i saw this for the first time. Why would we assign these callback functions to a dummy function doing nothing? Do not worry about this too much, the dummy function is simply a placeholder for the callback functions until they are registered by Angular.

The last step in the creation of our TextInputComponent is to create the getter and setter functions that will enable us to bind our component value to ngModel.

get value(): any {
    return this.InnerValue;
};

set value(v: any) {
    if (v !== this.InnerValue) {
        this.InnerValue = v;
        this.onChangeCallback(v);
    }
}

TextInput.html

Now that we have completed the TextInputComponent, we can create the html template

Sample Image - maximum width is 600 pixels

This is a very simple text input control where we display the validation rules based on the input parameters we configure and I won't go into to much detail, there is however one very important binding I want to highlight

[(ngModel)]="value"

Here we bind ngModel to the value getter and setter functions of our TextInputComponent

TextInputModule.ts

Now let's take care of the boring work and create the TextInputModule

import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { TextInputComponent } from '../Components/TextInputComponent';

@NgModule({
    imports: [BrowserModule, FormsModule],
    declarations: [TextInputComponent],
    exports: [TextInputComponent],
    bootstrap: [TextInputComponent]
})
export class TextInputModule { }

Make sure that you the component is exported, this will make the component reusable by other components

exports: [TextInputComponent]

How do I use the component?

First off all import the component in the module class of the component that will use it, let's call it SomeComponent

import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { SomeComponent } from '../Components/SomeComponent';
import { TextInputModule } from '../Modules/TextInputModule';

@NgModule({
    imports: [BrowserModule, FormsModule, HttpModule, TextInputModule],
    declarations: [SomeComponent],
    bootstrap: [SomeComponent]
})
export class SomeModule { }

Then configure the component in the HTML template of SomeComponent

Sample Image - maximum width is 600 pixels

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Product Manager Cellsure
South Africa South Africa
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --