Implementing nested custom controls in Angular 5

Implementation, modularization, and testing of nested custom controls for using with Reactive Forms and Template-Driven Forms

Akvelon is a business and technology solutions company that specializes in applying cutting-edge technologies to problems in fields as diverse as mobile technology, finance, and healthcare.
Akvelon company official logo

Motivation

Very often we embed third-party component libraries in Angular projects. But no matter how cool library is, sometimes one needs to modify library components UI while keeping the component and added styling separated, to enable easy component upgrade and solution management. Here we present a way of overriding isolated component styles, e.g making style changes without direct modification to third-party component. Moreover, we will isolate customized components by wrapping them to modules. Modularity is one of the greatest features of Angular, allowing us to keep project maintainable, clean and extensible.

Problem Statement

On one of the projects here in Akvelon, we applied Project Clarity components library on front-end side. All looked good but appearance of some components did not match requirements. These were inputs and selectboxes which should have borders on all sides, have same sizes and so on. Besides, HTML forms contain many controls supplemented by the labels which increase code duplication.

Design of components provided by library
Components according to the requirements

Requirements

At design time we highlighted features custom components should have. So, they should:

  • Work with Reactive Forms and Template-Driven Forms.
  • Validate user input.
  • Support two-way data binding with ngModel as we have stand-alone components outside of the forms.
  • Allow customization by applying styles per emerging requirements.
  • Able being marked as required when it is necessary (appending asterisk and make elements red or something like that).

In order to meet the requirements, we came to the following solution.

Solution

The most reliable approach is to create separate components for each element:

  • custom-input
  • custom-selectbox
  • custom-labeled
  • custom-labeled-input
  • custom-labeled-selectbox

We detected that inputs and selectboxes are the most widely used controls in our project. That’s why we created separate components for them. custom-labeled appends label to the pushed component, it is kind of a base part. custom-labeled-* components intended to unite custom-labeled component and corresponding custom-* component.

As a benefit keeping these building blocks as separate components decreased the amount of HTML code in template files significantly. See the difference in the following code snippets.

Using custom components
Without custom components

Additional Improvements

It is a good idea to wrap such things in their own modules, which will be also covered below. Additionally, we will take a look at the unit tests of components, especially with two-way data binding.

Implementation

In this article, implementation steps will be performed only for input related components. Here we are going to implement InputComponent which will keep customized input control. The next step is creating base LabeledComponent. It will keep customized label and attach it to child component. One more component should connect two mentioned components, that’s why we will create LabeledInputComponent. Finally, we will add them to special components module and cover by unit tests. You can explore the code which also includes selectboxes related stuff on GitHub.

Create InputComponent

According to our strategy, we will create custom input component with the following properties and event bindings. We will allow user to change only narrow set of properties, namely value, type, id, and placeholder.

Connect Angular forms API with DOM element

To tell Angular how to access the value of custom component, we have to provide the implementation of ControlValueAccessor interface.

The interface includes three method declarations:

  • writeValue called to write data from the model to the view
  • registerOnChange registers reaction on changing in the UI
  • registerOnTouched registers reaction on receiving a blur event (is emitted when an element has lost focus)
  • setDisabledState called on Disabled status changes

Let’s implement them.

Provide ControlValueAccessor for component

Now we have to tell the component to register itself as NG_VALUE_ACCESSOR provider. To implement it we have to include the corresponding provider into component’s configuration metadata.

After the change, the value of custom-input will be visible, for example, via the {{ }} syntax. Now we are able to change styles of the custom component and use it throughout the project.

Create LabeledComponent base

As mentioned before, this is a parent component containing child components using ng-content. Here required class simply appends asterisk on the label.

Create LabeledInputComponent

Now we are ready to create labeled component, combining LabeledComponent and InputComponent. It has to implement ControlValueAccessor and register itself as a provider to support two-way data binding.

Create separate module for custom components

Modules in Angular applications help to separate responsibilities of project parts, make project maintainable and so on. Our custom components are good candidates to be pushed into their own module. They are responsible for representing data. Let’s create a new module using angular cli command: ng g module custom-controls. There we will declare necessary components and export just those we want to share.

Add unit tests

Unit testing is a valuable aspect in the software development lifecycle. We have to be sure that all created components work as expected. Here we are checking correctness by bounding data to a host component, such as InputComponent which is tested below. Inside a test we created new components which hosts only component we want to test. Tests for labeled components will look pretty much the same. You can find detailed description of component inside a test host testing approach on Angular docs.

Conclusion

We implemented approach which makes components customizable and easy to use. Through components nesting we can easily change styling of any UI element type according to new requirements only in one place thus keeping uniformity. Addition of unit tests and wrapping components in a separate module makes it a good starting point for making your own components library and using it in multiple projects.

You can find the source code on GitHub repository or check it right away on Stackblitz.