Neat and accessible select using Angular with animations (Part 1)
Tags: angular, a11y, select, animations
Published: 2019-10-18 12:21:58

---
title: Neat and accessible select using Angular with animations (Part 1)
published: true
description: 
tags: angular, accessibility, select, animations
---

# Summary
For my very first post, I got inspired by an ui element from an application I worked on for my daily job. We are going to build a **select** component which I found really appealing and which allows to dive into the Angular animations API while caring for accessibility along the way.

![Neat and accessible select demo](https://thepracticaldev.s3.amazonaws.com/i/fq8jnrncxwtcp0s2emdm.gif)

You can directly go to the [source code](https://github.com/frontdatacloud/neat-select) if you prefer, and don't hesitate to contribute if you find it perfectible.
A sample demo is also hosted [here](https://neat-select.netlify.com) if you want to play with it.

# Step 1: Project initialization

Let's start by bootstraping a new Angular project with **scss** as the CSS preprocessor, and serving the app:
```bash
ng new neat-app --style scss
cd neat-app
ng serve -o
```


# Step 2: The building blocks

The markup consist of two separate components:
- a **neat-select** component: which encapsulates a native html **select** element (for accessibility purposes) with enhanced styling.
- a **neat-options** component: it will be used as a child component inside the **neat-select** one. It will iterate through the list of options and display them.

## Step 2.1: The **neat-options** component
Here is a peek at the **neat-options** final look:
![Options component](https://thepracticaldev.s3.amazonaws.com/i/4hk6tpqka07xkfkktkdb.PNG)

And the corresponding html/scss/ts code :

### options.component.html
```html
<div class="container">
  <label class="option" *ngFor="let option of options">
    <span>
      {{ option }}
    </span>
    <neat-radio
      [checked]="option === value"
      [name]="name"
      (valueChange)="onChange(option)"
    ></neat-radio>
  </label>
</div>
```
The **span** and the **neat-radio** elements are contained inside a **label** element so that a click on the span's text could also trigger the radio *valueChange* event.

### options.component.scss
```scss
:host {
  display: block;
  padding: 1rem;
  .container {
    max-width: 420px;
    margin: auto;
    .option {
      height: 64px;
      display: flex;
      align-items: center;
      justify-content: space-between;
      border-bottom: 2px solid var(--color-border-bottom);
      &:focus-within {
        outline: var(--outline);
      }
      neat-radio {
        margin-left: 1rem;
        --outline: none;
      }
    }
  }
}
```
A **max-width** is set to the container **div** to make the component responsive. This way it won't stretch accross the entire screen width for large screen sizes.

### options.component.ts
```ts
import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'neat-options',
  templateUrl: './options.component.html',
  styleUrls: ['./options.component.scss']
})
export class OptionsComponent {
  @Input() value: string;
  @Input() options: string[] = [];
  name = Date.now().toString();

  @Output() onSelected = new EventEmitter<string>();

  onChange(option: string) {
    this.value = option;
    this.onSelected.emit(this.value);
  }
}
```
The **onSelected** event emits the selected value whenever the option is changed.


## Step 2.2: The **neat-select** component
This is a screenshot of a page containing 3 **neat-select** components. The first two have a selected value unlike the third one.
![Select component](https://thepracticaldev.s3.amazonaws.com/i/8q4pzvvotsjm5e4ih05x.PNG)

### select.component.html
```html
<label>
  <div class="left-container">
    <span class="selected-option">{{ value }}</span>
    <span class="label">{{ label }}</span>
  </div>
  <select
    #select
    [value]="value"
    [required]="required"
    (click)="showOptions($event)"
    (keydown.enter)="showOptions($event)"
    (change)="change($event)"
  >
    <option *ngFor="let option of options" [value]="option">{{
      option
    }}</option>
  </select>
  <neat-icon [name]="'misc-arrow'" [size]="'xxsmall'"></neat-icon
></label>

<neat-options
  [@slideInOut]="opened ? 'visible' : 'hidden'"
  [options]="options"
  [value]="value"
  (onSelected)="onSelected($event)"
></neat-options>
```
This component encapsulates a native HTML **select** element.
The options list is iterated through by both the native **select** through its nested **option** elements and the **neat-options** component.

A *click* or a *keydown.enter* (for keyboard usage) on the **select** will show the **neat-options** component following a smooth transition.

When an option is selected from the then visible **neat-options** component, the corresponding value is bound to the native **select** value through the **onSelected** event listener.

### select.component.scss
```scss
:host {
  font-family: Pluto-Regular;
  max-width: 272px;

  label {
    position: relative;
    height: 64px;
    overflow: hidden;
    display: grid;
    grid-gap: 1rem;
    align-items: center;
    grid-template-columns: auto 64px;

    &:focus-within {
      outline: var(--outline);
    }

    .left-container {
      display: flex;
      height: 100%;
      flex-direction: column;
      justify-content: space-evenly;
      font-size: 1rem;

      .selected-option {
        transition: font-size 0.5s ease-in-out;

        &:empty {
          display: none;
        }
        &:not(:empty) ~ .label {
          font-size: 0.9rem;
          color: #5a6872;
          font-family: SF-Pro-Display-Regular;
        }
      }

      .label {
        order: -1;
      }
    }

    select {
      opacity: 0;
      position: absolute;
      width: 100%;
      height: 100%;
      z-index: -1;
      &:focus {
        outline: none;
      }
      option {
        display: none;
      }
    }

    neat-icon {
      justify-self: flex-end;
    }
  }

  neat-options {
    position: fixed;
    z-index: 1;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    overflow-y: auto;
    background: var(--color-white);
  }
}
```
The native html **select** element is hidden with `opacity: 0`so that it can still be focused. An outline is added to the **label** parent element when the **select** is focused.

### select.component.ts
```ts
import { trigger, state, style, transition, animate } from '@angular/animations';

@Component({
  selector: 'neat-select',
  templateUrl: './select.component.html',
  styleUrls: ['./select.component.scss'],
  animations: [
    trigger('slideInOut', [
      state(
        'visible',
        style({
          transform: 'translateX(0)',
          visibility: 'visible'
        })
      ),
      state(
        'hidden',
        style({
          transform: 'translateX(100%)',
          visibility: 'hidden'
        })
      ),
      transition('hidden => visible', animate('0.25s ease-out')),
      transition('visible => hidden', animate('0.25s ease-in'))
    ])
  ]
})
export class SelectComponent implements OnInit, ControlValueAccessor {
  @Input() label = '';
  @Input() required = false;
  @Input() options: string[];
  @Input() value: string;
  opened = false;

  @ViewChild('select', { static: false }) select: ElementRef<HTMLSelectElement>;

  ngOnInit() {}

  onSelected(option: string) {
    this.opened = false;
    this.select.nativeElement.focus();
  }

  showOptions(event: any) {
    event.preventDefault();
    this.opened = true;
  }
}
```

As previously aforementioned, this component is in charge of showing and hiding the **neat-options** component with smooth transitions.

To use angular's animations API, we start by defining the different **states** that the animated component can be in, then, we specify the type of **transition** we want from one state to another.

![Angular animations diagram](https://thepracticaldev.s3.amazonaws.com/i/qg7l8ru1gtuqk924xdan.PNG)
 

Here the **neat-options** component inside this **neat-select** is either in the *visible* state or the *hidden* state. It is hidden by defaut (by applying a `tranform: translateX(100%)`) and will become visible (by applying a `transform: translateX(0)`) when clicking on the **neat-select**.

A *transform* is used here instead of a *display: none* for performance reasons. Especially when dealing with a large list of options, adding and removing the whole list to and from the DOM would have a negative impact on the perceived performance.

You can notice that we also added a `visibility: hidden` to the **hidden** state, this is important because we don't want the **neat-radio** buttons inside the **neat-options** component to be focusable while the latter is offscreen.

One last point not to be forgotten is to give the focus back to the **select** element when the **neat-options** goes back in the hidden state following an option selection.


# Step 3: Tying it all together
Now that we have our **neat-select** component implemented, we are going to use it inside a form in our **app.component** page.

### app.component.html
```html
<form [formGroup]="formGroup">
  <h2 class="title">Neat and accessible select using Angular with animations</h2>

  <neat-select
    label="Random takes"
    [options]="optionList"
    formControlName="option1"
  ></neat-select>
  <neat-select
    label="Random takes"
    [options]="optionList"
    formControlName="option2"
  ></neat-select>
  <neat-select
    label="Random takes"
    [options]="optionList"
    formControlName="option3"
  ></neat-select>
</form>
```

### app.component.scss
```scss
form {
  padding: 1rem;
  .title {
    font-family: SF-Pro-Display-Regular;
    text-align: center;
    font-size: 2rem;
    margin-bottom: 4rem;
  }
}
```

### app.component.ts
```ts
import { FormGroup, FormBuilder } from '@angular/forms';

@Component({
  selector: 'neat-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  formGroup: FormGroup = this.fb.group({
    option1: [''],
    option2: [''],
    option3: ['']
  });

  constructor(private fb: FormBuilder) {}

  optionList = [
    'HTML CSS and Javascript',
    'Learn the basics',
    'They are as important as knowing a framework',
    'Angular reactive forms',
    'They are really powerfull',
    'Got mad love for animations',
    'You shoud checkout the Rust language',
    'Rust is cool but quite challenging at first',
    'It can be compiled to WebAssembly and run on the web',
    'WebGL is great for graphics on the web',
    'Combine Rust, Wasm and WebGL',
    'And you will build great and performant apps',
    'Directly avaible in a browser',
    'The Cloud humm..',
    'It opens a whole category of computing',
    'Serverless is king',
    'Especially for iterating fast',
    'Machine Learning',
    'Another intersting topic',
    'Can make the difference'
  ];
}
```
Nothing fancy here, just a classic Angular *FormGroup* with three *FormControls* corresponding to the three **neat-select**.

# Step 4: Furthermore
We used a **neat-radio** and a **neat-icon** in this tutorial without mentioning the internals. Also the **neat-select** component is configured to work nicely with Angular reactive forms by implementing the **ControlValueAccessor** interface (not shown in the code blocks). Those topics will be covered in the second part of this tutorial wich will be avaible someday during the following week, so stay tuned ;) !


# Credits
- UI Design by [Pierre Huard](https://www.linkedin.com/in/pihuard/)
- Review and recommendations by [Wassim Chegham](https://twitter.com/manekinekko)

# About me
I am a Software Engineer who does FrontEnd work with **Angular** in my daily job. I am also currently working with **Rust** compiled to WebAssembly(**Wasm**) and with **WebGL** for a side project.
You can follow me on [twitter](https://twitter.com/frontdatacloud) or [linkedin](https://www.linkedin.com/in/fidele-palouki/) if you want to keep in touch. I plan to publish tutorials like this one more often.