Angular Material Autocomplete with API Call

Overview

This is an example of how to build an Autocomplete Field with Angular Material. To keep this simple, we will use JSON data from a file and a service that fetches data to simulate an API call.

Please get the complete source files from https://github.com/fullstacksoup/blog-ng-material-autocomplete.git.

Get Source Files


Remember to CD into the application folder and run npm install before ng server from the root directory.

Prerequisites

Install Angular Material.

ng add @angular/material

JSON Data

Add a JSON file under the assets folder as ‘assets/state.json’.

Add the following to your JSON file. Get the full JSON file from the GitHub example.

[
  {
      "name": "Alabama",
      "abbreviation": "AL"
  },
  {
      "name": "Alaska",
      "abbreviation": "AK"
  },
  {
      "name": "American Samoa",
      "abbreviation": "AS"
  },
  {
      "name": "Arizona",
      "abbreviation": "AZ"
  },
  {
      "name": "Arkansas",
      "abbreviation": "AR"
  },
  {
      "name": "California",
      "abbreviation": "CA"
  },
  {
      "name": "Colorado",
      "abbreviation": "CO"
  }
]

HTTP Service

Next create a service for getting the country data.

ng g c services/geo-location

For geo-location-service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable()
export class GeoLocationService {

  constructor(private http: HttpClient) { }

  //  Get JSON Data from file - states.json under src/assets/
  public getStates(): Observable<any> {
    return this.http.get('assets/states.json');
  }
}

Autocomplete Component

Create a new component called autocomplete-field-countries.

ng g c components/json-autocomplete-field

Typescript

The autocomplete-field-states.component.ts file add the following.

import { HttpErrorResponse } from '@angular/common/http';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
import { GeoLocationService } from 'src/app/services/geo-location.service';
import { FormControl } from '@angular/forms';
import { map, startWith } from 'rxjs/operators';
import { Observable } from 'rxjs/internal/Observable';
import { MatSnackBar } from '@angular/material/snack-bar';

export interface USStates {
  name: string;
  abbreviation: string;
}

@Component({
  selector: 'json-autocomplete-field',
  templateUrl: './json-autocomplete-field.component.html',
  styleUrls: ['./json-autocomplete-field.component.scss']
})
export class JsonAutocompleteFieldComponent implements OnInit, OnDestroy{
  private subs = new Subscription();
  options: USStates[] = [];
  filteredJSONDataOptions: Observable<any[]>;
  jsonControl = new FormControl();

  constructor(private geoSVC: GeoLocationService, private snackBar: MatSnackBar) {}

  ngOnInit(): void {
   this.subs.add(this.geoSVC.getStates().subscribe((data) => {
      this.options = data;
    },
    (err: HttpErrorResponse) => {
      console.log(err);
    }));

    this.filteredJSONDataOptions = this.jsonControl.valueChanges.pipe(
      startWith(''),
      map(value => this.json_data_filter(value))
    );
  }

  ngOnDestroy(): void {
    if (this.subs) {
      this.subs.unsubscribe();
    }
  }

  // JSON Data Filter
  private json_data_filter(value: string): string[] {
    const filterValue = value.toLowerCase();
    let newList = [];
    this.options.forEach(element => {
      if (element.name.toLowerCase().indexOf(value.toLowerCase()) !== -1) {
        newList.push({'name': element.name, 'abbreviation': element.abbreviation });
      }
    })
    return newList;
  }

  private openSnackBar(message: string): void {
    this.snackBar.open(message, 'X', {
      duration: 2000,
      verticalPosition: 'top'
    });
  }

  public onSelectState(value: string): void {
    this.openSnackBar(`State abbreviation selected: ${value}`)
  }
}

Important: The variable holding this information should be declared as type USStates[] = [] which is defined as an interface.

options: USStates[] = [];

HTML

Add the following code to autocomplete-field-countries.component.html.

<mat-form-field>
  <input type="text"
         placeholder="Pick a State"
         aria-label="Number"
         matInput
         [formControl]="jsonControl"
         [matAutocomplete]="auto">
  <mat-autocomplete autoActiveFirstOption #auto="matAutocomplete">
    <mat-option *ngFor="let option of filteredJSONDataOptions | async" [value]="option.name" (onSelectionChange)="onSelectState(option.abbreviation)">
      {{option.name}}
    </mat-option>
  </mat-autocomplete>
</mat-form-field>
Photo by Lubo Minar on Unsplash