Angular Material Autocomplete

In this lecture let's take a look at autocomplete in angular material and also we'll see an angular material auto complete example in this article.

Import the module for angular material autocomplete

As always to work with angular material autocomplete you should firstly import the MatAutocompleteModule and add it to the MaterialComponents[] array in material.module.ts.

import { MatAutocompleteModule } from "@angular/material";

const MaterialComponents = [
  MatAutocompleteModule,
]

angular material autocomplete example

Now that we have imported the module let's create our first autocomplete example in HTML specifically in app.component.html file. So let's start with a <form> tag, within this tag let's add <mat-form-field> component and within this component let's add an input element of type text and don't forget to add the matInput attribute we have seen that in (angular material input lecture), so let's see our code firstly and then we'll keep going.

<form>
  <mat-form-field>
    <input type="text" matInput />
  </mat-form-field>
</form>

angular material autocomplete example It's a simple input element without any functionalities.

Now let's create something that really works, the first step is to create a list of options that will be displayed as we start typing in that input element in other words that will be our autocomplete example. So in app.component.ts logic file let's add 3 options for now that will be in an array of strings here is the code for that.

export class AppComponent {
  options: string[] = ["Angular", "React", "Vue"]; 
}

Next step is to make use of autocomplete to display this list of options, the responsible component for autocomplete is mat-autocomplete so right after the input element add that component and to specify the options we use the mat-option within the mat-autocomplete component and to iterate over the list of options that we create we'll be using the *ngFor directive from angular framework we are also going to have property binding to value which is going to equal the option which is from the current iteration and lastly display the options so let's see the full picture for that code again.

<form>
  <mat-form-field>
    <input type="text" matInput />
    <mat-autocomplete>
      <mat-option *ngFor="let opt of options" [value]="option">
        {{ option }}
      </mat-option>
    </mat-autocomplete>
  </mat-form-field>
</form>

Alright now we have the input element and the autocomplete component, the final step is to link together between those elements for that we need to add a reference variable on mat-autocomplete component and assign it to matAutocomplete as a value and on the input element we add this matAutocomplete as a property and bind it to our reference variable #auto, here is our code updated again.

<form>
  <mat-form-field>
    <input type="text" matInput [matAutocomplete]="auto" />
    <mat-autocomplete #auto="matAutocomplete">
      <mat-option *ngFor="let opt of options" [value]="option">
        {{ option }}
      </mat-option>
    </mat-autocomplete>
  </mat-form-field>
</form>

Now if you save and take a look at the browser when I click on the input element immediately we get the list of options displayed under the input element like this in the picture. basic autocomplete example When I click on specific option from the list it fills the value for the input element. So this a basic autocomplete example up and running.

Now it's also quite possible that your list of options is not simply an array of strings instead it could be an array of objects for example an array of 4 objects which has key value pairs like this.

export class AppComponent {
  options: string[] = ["Angular", "React", "Vue"];
  objectOptions = [
    { name: "Angular" },
    { name: "React" },
    { name: "Vue" },
    { name: "Angular Material" }
  ];
 }

So as you seen I create a new array called objectOptoins which has 4 objects each of the object have name. Now to display the options correctly in the HTML we interpolate option.name and of course make sure to iterate over objectOptoins array.

<form>
  <mat-form-field>
    <input type="text" matInput [matAutocomplete]="auto" />
    <mat-autocomplete #auto="matAutocomplete">
      <mat-option *ngFor="let option of objectOptions" [value]="option">
        {{ option.name }}
      </mat-option>
    </mat-autocomplete>
  </mat-form-field>
</form>

If you now save the file and take a look at the browser the options are displayed correctly but when you select an option the autocomplete doesn't display that value properly. angular auto complete To let the autocomplete know that is has to use the name property to display as value you can specify a display function so back in the component class in app.component.ts I'm going to define the display function which gets each object as parameter I'm gonna call it as subject and if at all there is a subject we return subject.name else return undefined, here is the code for that.

  displayFn(subject) {
    return subject ? subject.name : undefined;
  }

Now in the HTML on the mat-autocomplete component we add the displayWith directive and assign the function.

<form>
  <mat-form-field>
    <input type="text" matInput [matAutocomplete]="auto" />
    <mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn">
      <mat-option *ngFor="let option of objectOptions" [value]="option">
        {{ option.name }}
      </mat-option>
    </mat-autocomplete>
  </mat-form-field>
</form>

Now if you take look at the browser you should found that the autocomplete works as expected, the display value is presented correctly.

angular material filter autocomplete

You might want that as you start typing in the text the options are narrowed down. But this is where things starts to get a little complicated, to be able to implement a filtered autocomplete we need a decent knowledge of angular forms.

The first step is to import ReactiveFormsModule and add it to the imports[] array in app.module.ts.

import { FormsModule, ReactiveFormsModule } from "@angular/forms";
@NgModule({
  ....
  imports: [
    ....
    ReactiveFormsModule
  ],
  ....
})

The second step is to create a form control and link it to the input element so in app.component.ts which is the Class component we're going to import FormControl from @angular/forms and create a new form control.

import { FormControl } from '@angular/forms';

myControl = new FormControl();

Now on the input element in the HTML.

    <input
      type="text"
      matInput
      [matAutocomplete]="auto"
      [formControl]="myControl"
    />

So we created the form control and linked it to the element.

Third step we use the help of RxJs to create a filtered list of options, so in app.component.ts begin by making the necessary imports, we need Observable from rxjs and map, startWith operators from rxjs/operators.

import { Observable } from "rxjs";
import { map, startWith } from "rxjs/operators";

Next we need a new property in the Class which I'll call it filteredOptions which is going to be an observable of an array of strings.

filteredOptions: Observable<string[]>;

Next need to implement the ngOnInit life cycle hook, so I'm gonna import it firstly.

import { Component, OnInit } from "@angular/core";

Now AppComponent class is going to implement OnInit.

export class AppComponent implements OnInit {
    .....
}

Now we can define the life cycle hook method and within this method let's add some magic code.

  ngOnInit() {
    this.filteredOptions = this.myControl.valueChanges.pipe(
      startWith(""),
      map(value => this._filter(value))
    );
  }
  private _filter(value: string): string[] {
    const filterValue = value.toLowerCase();
    return this.options.filter(option =>
      option.toLowerCase().includes(filterValue)
    );
  }

So what is happening here is that whenever the input value changes the code in ngOnInit() method is called and in the _filter(value) method that we define we accept the filtered text and check if the option includes that filtered text if it does it return true and gets displayed else the option is hidden in the autocomplete.

Here is the final code for app.component.ts.

import { Component, OnInit } from "@angular/core";
import { FormControl } from "@angular/forms";

import { Observable } from "rxjs";
import { map, startWith } from "rxjs/operators";

@Component({
  selector: "app-root",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.scss"]
})
export class AppComponent implements OnInit {
  options: string[] = ["Angular", "React", "Vue"];
  filteredOptions: Observable<string[]>;
  objectOptions = [
    { name: "Angular" },
    { name: "React" },
    { name: "Vue" },
    { name: "Angular Material" }
  ];

  displayFn(subject) {
    return subject ? subject.name : undefined;
  }

  myControl = new FormControl();

  ngOnInit() {
    this.filteredOptions = this.myControl.valueChanges.pipe(
      startWith(""),
      map(value => this._filter(value))
    );
  }
  private _filter(value: string): string[] {
    const filterValue = value.toLowerCase();
    return this.options.filter(option =>
      option.toLowerCase().includes(filterValue)
    );
  }
}

The final step is to iterate over this in filtered options in the HTML with the async pipe so let's make some changes.

<form>
  <mat-form-field>
    <input
      type="text"
      matInput
      [matAutocomplete]="auto"
      [formControl]="myControl"
    />
    <mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn">
      <mat-option
        *ngFor="let option of filteredOptions | async"
        [value]="option"
      >
        {{ option }}
      </mat-option>
    </mat-autocomplete>
  </mat-form-field>
</form>

If you save all files and take a look at the browser we should have a filtered autocomplete working just fine when we start typing text in the input element. angular material filter autocomplete

Again if it seems hard to understand that because it requires some knowledge in angular forms.