In this Angular 9/8 post, we’ll discuss how to implement a TypeAhead or Auto Suggestion control in Angular application by using the @ng-select npm package module.
The Ng Select package is very popular to add a customized Single and Multi-select drop-down or select box with many type features and customization options.
You can check our other tutorial for implementation and adding features like Multiple select boxes with checkbox, Virtual Scroll for huge option performance, Custom search for multiple keys, etc.
By default, the ng-select box shows all values to the user when it is focused or clicked, but there is some situations where we don’t want to display all the options available and show only matching results as ser types like a type ahead or suggestion box.
Today, we’ll discuss how to show dynamic server fetched results in the ng-select control by making a server call as user types. It will also have min length property to search only if the user type at least 2 characters as a search term.
We’ll be using RxJS advanced method and operators to optimize the server result fetching using debounceTime
and distinctUntilChanged
to limit unnecessary HTTP calls
Let’s quickly create a new Angular application and setup @ng-select
package to demonstrate TypeAhead to fetch server response.
[lwptoc]
Create a new Angular project
Run following ng command to create a new Angular 9 project
$ ng new angular-ngselect-typeahead-app
? Would you like to add Angular routing? No
? Which stylesheet format would you like to use? SCSS
Move inside the Angular project
$ cd angular-ngselect-typeahead-app
Run Angular project
$ ng serve --open
Install ng-select
package
Now install the ng-select package module in the Angular project we created
$ npm install --save @ng-select/ng-select
Import ng-select in App Module
To use the ng-select
component, we need to import the NgSelectModule
in the App’s main module. For using ngModel
also import the FormsModule.
As results will be fetched from a server also import the HttpClientModule
to make HTTP get API calls
Open the app.module.ts file and make the following changes.
// app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { NgSelectModule } from '@ng-select/ng-select';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
NgSelectModule,
FormsModule,
HttpClientModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Add ng-select Style Theme
Finally, add a theme to style our awesome ng-select component.
We already have three themes available to select from. Import any of the following in the style.css file:
@import "~@ng-select/ng-select/themes/default.theme.css";
@import "~@ng-select/ng-select/themes/material.theme.css";
@import "~@ng-select/ng-select/themes/ant.design.theme.css";
Using TypeAhead in ng-select Box
Create an ng-select
box by adding the below component in the template
<ng-select
[items]="movies$ | async"
bindLabel="Title"
[trackByFn]="trackByFn"
[minTermLength]="minLengthTerm"
[loading]="moviesLoading"
typeToSearchText="Please enter {{minLengthTerm}} or more characters" [typeahead]="moviesInput$"
[(ngModel)]="selectedMovie">
</ng-select>
Let’s have a look at the properties we used in the ng-select
component to show remote server results in the select box
[items]
: This represents the Observable to keep track of the change in the items collection which we are fetching from the server by making HTTP get a call.bindTitle
: To display the property value after a value is selected by the user, By default, it shows thename
by here we want to showTitle
in the response Object.[trackByFn]
: Used to track the ID of the item selected. here we haveimdbID.
[minTermLength]
: It takes a number of characters typed by the user after which the result options are shown to select from.[loading]
: This displays the loading message to indicate the status of the result set to the user.typeToSearchText
: Message displayed to the user as a placeholder.[typeahead]
: This is the Subject that watches the change in the term typed by the user to fetch results from the server.
Update Template Class Component
In the app.component.ts file, we’ll make the following changes:
Add a few variables in the class
movies$: Observable<any>;
moviesLoading = false;
moviesInput$ = new Subject<string>();
selectedMovie: any;
minLengthTerm = 3;
The movies$
and moviesInput$
are Observable used to subscribe to changes in the collection object and input control respectively.
The loadMovies()
method is called on component init to subscribe to the change in the Object data fetched from the server.
loadMovies() {
this.movies$ = concat(
of([]), // default items
this.moviesInput$.pipe(
filter(res => {
return res !== null && res.length >= this.minLengthTerm
}),
distinctUntilChanged(),
debounceTime(800),
tap(() => this.moviesLoading = true),
switchMap(term => {
return this.getMovies(term).pipe(
catchError(() => of([])), // empty list on error
tap(() => this.moviesLoading = false)
)
})
)
);
}
The switchMap()
fetches the server result by calling the getMovies()
method by passing the term typed by the user.
Here we used three methods to control the HTTP get calls
filter()
: The event will be triggered only when the length of the input value is more than 2 or whatever you like.debounceTime()
: This operator takes time in milliseconds. This is the time between key events before a user stops typing.distinctUntilChanged()
: This operator checks whether the current input is sitting from a previously entered value. So that API will not hit if the current and previous value is the same
So the final component class will look like this
// app.component.ts
import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { concat, Observable, of, Subject, throwError } from 'rxjs';
import { catchError, debounceTime, distinctUntilChanged, switchMap, tap, map, filter } from 'rxjs/operators';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'angular-ngselect-typeahead-app';
movies$: Observable<any>;
moviesLoading = false;
moviesInput$ = new Subject<string>();
selectedMovie: any;
minLengthTerm = 3;
constructor(private http: HttpClient) {}
ngOnInit() {
this.loadMovies();
}
trackByFn(item: any) {
return item.imdbID;
}
loadMovies() {
this.movies$ = concat(
of([]), // default items
this.moviesInput$.pipe(
filter(res => {
return res !== null && res.length >= this.minLengthTerm
}),
distinctUntilChanged(),
debounceTime(800),
tap(() => this.moviesLoading = true),
switchMap(term => {
return this.getMovies(term).pipe(
catchError(() => of([])), // empty list on error
tap(() => this.moviesLoading = false)
)
})
)
);
}
getMovies(term: string = null): Observable<any> {
return this.http
.get<any>('http://www.omdbapi.com/?apikey=[YOUR_OMDB_KEY]&s=' + term)
.pipe(map(resp => {
if (resp.Error) {
throwError(resp.Error);
} else {
return resp.Search;
}
})
);
}
}
NOTE: We have used OMDB API for demo purposes. You can get your own key for free by simply adding your email.
Now run your project to see it working by hitting $ ng serve --open
Conclusion
Using ng-select we can add a powerful yet optimized select box for Single or Multiple selection control. Here we discussed how to display dynamic server response objects in the ng-select components by using Observable. To implement this we added [typeahead] and [minTermLength] properties.
Also, we used RxJS methods to add debountTime methods to further optimize the server HTTP calls.
I hope you enjoyed this tutorial and it was helpful. Do share your comments and feedback.
Stay Safe Stay Happy 🙂
Leave a Reply