Create an Angular custom input component with grouping

Learn to create your own tags like control

In this article, we will create an Angular component that takes in some text from the user, and as the user presses the Enter key, we will store that value into a visual representation that looks like the picture below:

image.png

Here is demo link. Note that the component in the demo is within a parent with a width of 200px. This is so you can play around with line wrapping.

To make things simple so we can focus only on the control functionality, I will be skipping any typeahead dropdown features (maybe we can do that in the future), and I will also be skipping any fancy UI implementations (such as adding an X on the grouped countries). I will also refrain from using any third party library.

This article is purposefully kept simple - the idea is to give you the building blocks.

Note: In the screenshot above, Canada, Japan and Algeria are 3 groups created by the control.

Features of the control:

  1. User can enter a string text and press enter to save it as a group.
  2. Groups cannot be edited, but can be deleted by double-clicking on them.
  3. Text can be entered on the right of the last group item.
  4. Pressing backspace when nothing has been typed will erase the previous group.
  5. Entering a long line of text that is longer than the control width will break the text to the next line.
  6. Users can enter as many items as they want.
  7. Groups will be available in an array.

Missing features: This control does not take a data source to validate against, nor does it display and dropdown for user to select from. Furthermore, I have not focused on updating this control to work with Angular forms. This can by implementing the required interface.

Ideas:

  • To allow a user to type, we need a text input.
  • To show groups, we need to have a container, such as a div that shows up before the text input.
  • Groups will show before the text box, and as such, the two need to be placed next to each other - possibly use some flexbox styling. This means, we need to create a parent div that will hold our group and our text input.
  • Groups need to act as a singular text - we need to save group values (such as Canada, Mexico, Japan, etc). This can be done by using an array. We can iterate through the array and show them inside that div.
  • To add a new group, we can listen to the 'enter' keydown event of the text box and store input text value into our groups array.
  • To remove previous groups using backspace, we can use the 'backspace' keydown event on the text box. If our text input doesn't have any value, we pop a group out of the array.
  • To hide the textbox, we can remove the border of the text input and also remove the outline. We can then place border on the parent div (which contains our group and input).
  • To allow for groups that have text that is longer than the width of our control, we can place an overflow style on our parent, with the value of 'scroll'. We then set our group to have a word-break value of break-all.
  • To allow our control to have multiple lines, we can set our parent to have flex-wrap of 'wrap'.
  • We can add line-height to our groups. This will make sure that wrapping to next line creates a space between the two lines.

Code

Here is our component template structure:

<div class="ig-container">
  <div *ngFor="let group of groups">
    <span class="ig-groups" (dblclick)="remove(group)">{{
      group
    }}</span>
  </div>
  <input
    class="ig-input"
    [(ngModel)]="userInput"
    style=""
    type="text"
    (keydown.backspace)="removeLastGroup()"
    (keydown.enter)="createGroup()"
  />
</div>

And here are the styles:

.ig-container {
  display: flex; 
  flex-wrap:wrap;
  border: 1px solid black;
  padding-bottom: 4px;
  padding:4px;

}

.ig-groups {
  margin:4px 2px;
  padding:2px;
  border: 1px solid gray;
  line-height:30px;
  cursor: pointer;
  word-break: break-all;
}

.ig-input {
  border: 0px solid black;
  outline:none;
  flex-grow: 1;
  width: 20px;
}

We have our parent that is set to display of flexbox. This allows us to place the children (groups and input) next to each other. The input element's border and outline is removed, and the parent is given a border. We let the input control grow to take remaining horizontal space, and we set our parent to wrap.

Also notice that we used ngModel. I did not want to pass the value of our input text through every method, so I decided to make it a two way binding. This way, when a user presses the enter key, I can just clear the userInput variable (and the UI will update automatically).

Here is the typescript file:

import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  userInput: string = '';
  groups = [];

  createGroup() {
    this.userInput.length > 0 ? this.groups.push(this.userInput): null;
    this.userInput = '';
  }

  remove(value) {
    this.groups = this.groups.filter(selection => selection !== value);
  }

  removeLastGroup(){
    if(!this.userInput && this.groups.length > 0) {
      this.groups.pop();
      return;
    }
  }
}

The typescript file should be self explanatory now. When a user presses the enter key on our input field, we call the createGroup method. All we want to do in this method is to add our user input into our group array if validation passes.

If the backspace key is pressed, we call the removeLastGroup method. This function does some validation and pops item out of our array (which turns out to be the last item added).

The remove method is called when a user double-clicks on a group. All we do here is to remove the group and assign our new array to the groups variable.

Improvements

There are a number of improvements that can be made to this component, including:

  • Display matching data from a dropdown and allow user to select from the list
  • Add an 'x' button on top of the group to indicate that the group can be removed
  • Allow users to double click on the group to make it editable
  • Allow the control to not wrap but to scroll to the right (by tweaking some styles).
  • Move component into a library
  • Add unit tests