Create a Custom Select Component in Angular, Complete with Virtual Scrolling

Netanel Basal
Netanel Basal
Published in
6 min readMay 14, 2019

--

In this article, I will walk you through the process of creating a custom select component. Along the way, we’ll use several techniques to make our component fast and flexible.

As always, here’s an illustration of the end result:

Let the fun begin 🎬

Creating the Select Component

Let’s create the select component, which takes an array of options as input. Each element in the array should be an object containing at least one unique id field and a label field.

Moreover, we’ll add two inputs — idKey, which defaults to id, and labelKey, which defaults to label. This will allow the user to change these when required.

Note that we also clone the passed options array so that we can filter them later without modifying the original array.

Next, we need to maintain a model property that will hold the selected option id. We’re going to keep it simple and won’t create a custom form control for our select component, but you can add it fairly easily by following this article.

First, we check if the user passes an initial option id through the model input property. If that’s the case, we search for a matching option and set it as the selected one.

Next, we create a getter named label that determines if we should display the selected option label or a placeholder based on the model value.

Let’s see how we can use it in the template:

We have a trigger button that contains two elements. When the dropdown is closed, we display the selected option label (or placeholder), and when it’s open, we show the search input.

In addition, we define a click handler, passing the button native element reference and the dropdown template. We’ll see later how we use it. Keep that in mind.

Let’s continue by creating the search functionality:

We create the searchControl property, which is bounded to the search input formControl directive. When the control’s value changes, after applying a debounce, we call the search() method passing the search term. We can then set the options property based on the obtained results.

Now that we’re done with the boring things, we’re ready to get down to business. 👇

Creating the Dropdown Template — Virtual Scroll

A typical dropdown can have many options. However, problems arise when elements are in the DOM, causing slow initial rendering and laggy scrolling, and dirty checking on each one of them can be expensive.

The solution to this problem is to use a well-known technique called Virtual Scroll.

The central concept behind virtual rendering is to render only visible items.
For example, if there are thousands of options in our dropdown, it’s more efficient to load only the elements that are visible and unload them when they aren’t by replacing them with new ones.

Let’s implement this feature in our select component by employing the Angular’s CDK API:

The <cdk-virtual-scroll-viewport> component displays large lists of elements performantly by only rendering the items that fit on-screen.

The *cdkVirtualFor structural directive replaces *ngFor inside of a <cdk-virtual-scroll-viewport>, supporting the same API as *ngFor.

The most straightforward usage specifies the list of items and the itemSize property that must be set.

We also define a click handler on each dropdown item:

When the user selects an option, we update the model property, emitting the new select option, and close the menu. We also want to mark the selected option, so we add the active class based on whether the current option is active:

Creating the Floating Dropdown

Now we need to create the select dropdown when the user clicks on the trigger button. In previous articles, I have explained in detail how to create floating elements using the CDK overlay API. This time, we’ll explore a different approach.

Instead, we’ll use a popular library called Popper. Let me tell you a secret. I created a core components library for our application and only used Popper to create floating elements, such as autocomplete, menus, popover, etc. I did this because I like the simplicity that comes with Popper.

Let’s examine the open() method implementation:

First, as we saw earlier in the select component template, we pass the dropdown template reference and the origin button element to the open() method. Now that we have a template, we need to create it by using the ViewContainerRef createEmbededView() method.

At this point, the template has been instantiated, so we can obtain a reference to the native root DOM element from the view’s rootNodes property, which in our case, is the select dropdown element.

Then, we move it to the location in the DOM where we want it to be rendered. In our case, we append it to the body because we want to avoid cases when a parent component has an overflow: hidden or z-index style, but we need the child to visually “break out” of its container.

Next, we set the dropdown width to be the same width as the origin element.

Finally, we create a new Popper instance outside of the Angular zone as we don’t want internal Popper events like scrolling, to cause a change detection cycle. By default, the Popper position is set to bottom. You can expose a placement input to give more control to the consumer.

It’s as simple as that.

The observant among you may have noticed to a specific side-effect. When we perform a search, the dropdown’s height doesn’t change accordingly to fit the length of the new visible items, which is not what we want.

This happens because we must define a fixed height to use the virtual scroll component. Luckily, there is a workaround we can use. We can set the dropdown height dynamically based on the length of the visible items:

Sweet 🍬🍯

However, designers will probably want more…

As we all know, designers will not always be satisfied with the idea of always displaying just a label. For example, they might want to display an icon next to the option.

No worries, Angular makes implement it a breeze. We can create a new input property that receives a reference to a template, through which we expose a reference to the current option using the context API:

Now, we can use any custom HTML that we want:

Let’s close the article with the close() method implementation:

🤓 🚀 Have You Tried Akita Yet?

One of the leading state management libraries, Akita has been used in countless production environments. It’s constantly developing and improving.

Whether it’s entities arriving from the server or UI state data, Akita has custom-built stores, powerful tools, and tailor-made plugins, which help you manage the data and negate the need for massive amounts of boilerplate code. We/I highly recommend you try it out.

Here’s the complete source code:

Follow me on Medium or Twitter to read more about Angular, Akita and JS!

--

--

A FrontEnd Tech Lead, blogger, and open source maintainer. The founder of ngneat, husband and father.