Async material autocomplete in Angular
Tags: angular, javascript, typescript, rxjs
Published: 2021-04-01 18:38:03

### Topic
While working on my company project, I get the task of making a country selector. The project is using Angular with Angular Material. This is how I made it.

### Prerequisites
For the demo version, I will do a simple angular project with that field only.
To make Angular project type in the command line:

```
ng new async-autocomplete
```

I also used the default Angular Material setup by typing.

```
ng add @angular/material
```

Now my demo project is ready.

### Http Service
To be able to make HTTP calls in my `AppModule` I imported `HttpClientModule` from `@angular/common/HTTP`.
In the app directory, I generated a service which is used for making HTTP call. I typed the command:

```
ng g service country
```

which produced the `country.service.ts` file for me.
In that service, I used `HttpClient` in the constructor imported from `@angular/common/http`.
Method for getting countries list
```typescript
getByName(name: string): Observable<string[]> {
    return this.http
      .get<Country[]>(`https://restcountries.eu/rest/v2/name/${name}`)
      .pipe(map(countryList => countryList.map(({ name }) => name)));
  }
```

* `Country` is just a simple interface with the `name` property.
* [Here](https://restcountries.eu/) is the documentation for the URL which I used. 
* `map` is an [operator](https://rxjs.dev/api/operators/map) for mapping value inside observable (I am just pulling out country name)

### The input
For the field I imported 3 modules in `AppModule`:
* `MatFormFieldModule` and `MatInputModule` is used by the field
* `MatAutocompleteModule` for autocompletion
* `ReactiveFormsModule` because the field is used inside reactive form.

The HTML template is quite simple:
```HTML
<form [formGroup]="form">

  <mat-form-field appearance="fill">
    <mat-label>Name</mat-label>
    <input matInput formControlName="name" [matAutocomplete]="auto">
  </mat-form-field>

</form>

<mat-autocomplete #auto="matAutocomplete">
  <mat-option *ngFor="let countryName of countries$ | async" [value]="countryName">
    {{countryName}}
  </mat-option>
</mat-autocomplete>
```
There are two important things:
* `[matAutocomplete]="auto"` is an attribute which connects field with autocompletion list
* `async` pipe, which subscribes to observable and unsubscribe when the component is destroyed.

My component ts code has two properties:
```typescript
  countries$: Observable<string[]>;
  form = this.formBuilder.group({
    name: [null],
  });
```
* `countries$` which holds my countries list
* `form` reactive form definition

In constructor definition:
```typescript
  constructor(
    private formBuilder: FormBuilder,
    private countryService: CountryService,
  ) {
```
* `formBuilder` for reactive form creation
* `countryService` for using the HTTP method defined in service.

On every input value change, I am switching to service to make GET call for a list and I am assigning it to my observable:
```typescript
    this.countries$ = this.form.get('name')!.valueChanges.pipe(
      distinctUntilChanged(),
      debounceTime(1000),
      filter((name) => !!name),
      switchMap(name => this.countryService.getByName(name))
    );
```
* `valueChanges` which triggers every value change (It is an Observable)
* `distinctUntilChanged` [operator](https://rxjs.dev/api/operators/distinctUntilChanged) which emits only when the value is different than the previous one (avoid making requests for the same name one after another)
* `debounceTime` [operator](https://rxjs.dev/api/operators/debounceTime) to avoid spamming API with too many calls in a short time (It waits 1000ms and if the value is not emitted, then emits last value)
* `filter` [operator](https://rxjs.dev/api/operators/filter) which checks if there is the value (avoid HTTP calls with no name)
* `switchMap` [operator](https://rxjs.dev/api/operators/switchMap) which is changing from one observable (`valueChanges`) to another (`getByName` from service).

Full TS code:
```typescript
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { Observable } from 'rxjs';
import { debounceTime, distinctUntilChanged, filter, switchMap } from 'rxjs/operators';
import { CountryService } from './country.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  countries$: Observable<string[]>;
  form = this.formBuilder.group({
    name: [null],
  });

  constructor(
    private formBuilder: FormBuilder,
    private countryService: CountryService,
  ) {
    this.countries$ = this.form.get('name')!.valueChanges.pipe(
      distinctUntilChanged(),
      debounceTime(1000),
      filter((name) => !!name),
      switchMap(name => this.countryService.getByName(name))
    );
  }
}

```

[Link](https://github.com/TomWebwalker/async-autocomplete) to repo.