Autocomplete
Angular Bootstrap 5 Autocomplete component
Autocomplete component predicts the words being typed based on the first few letters given by the user. You can search the list using basic scroll and the keyboard arrows.
Note: Read the API tab to find all available options and advanced customization
Basic example
<mdb-form-control style="width: 22rem">
<input
mdbInput
[ngModel]="searchText | async"
(ngModelChange)="searchText.next($event)"
[mdbAutocomplete]="autocomplete"
type="text"
id="autocomplete"
class="form-control"
/>
<label mdbLabel class="form-label" for="autocomplete">Example label</label>
</mdb-form-control>
<mdb-autocomplete #autocomplete="mdbAutocomplete">
<mdb-option *ngFor="let option of results | async" [value]="option">
{{ option }}
</mdb-option>
<div *ngIf="notFound" class="autocomplete-no-results">No results found</div>
</mdb-autocomplete>
import { Component } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { map, startWith, tap } from 'rxjs/operators';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
searchText = new Subject<string>();
results: Observable<string[]>;
notFound = false;
data = ['One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight'];
constructor() {
this.results = this.searchText.pipe(
startWith(''),
map((value: string) => this.filter(value)),
tap((results: string[]) =>
results.length > 0 ? (this.notFound = false) : (this.notFound = true)
)
);
}
filter(value: string): string[] {
const filterValue = value.toLowerCase();
return this.data.filter((item: string) =>
item.toLowerCase().includes(filterValue)
);
}
}
Display value
The displayValue
option allow to separate original result value from the value
that will be displayed in the result list or input (after selection). Its useful when the data
returned by the filter method is an array of objects. You can specify which
parameter of the object should be displayed.
<mdb-form-control style="width: 22rem">
<input
mdbInput
[ngModel]="searchText | async"
(ngModelChange)="searchText.next($event)"
[mdbAutocomplete]="autocomplete"
type="text"
id="autocomplete"
class="form-control"
/>
<label mdbLabel class="form-label" for="autocomplete">Example label</label>
</mdb-form-control>
<mdb-autocomplete #autocomplete="mdbAutocomplete" [displayValue]="displayValue">
<mdb-option *ngFor="let option of results | async" [value]="option">
{{ option.title }}
</mdb-option>
<div *ngIf="notFound" class="autocomplete-no-results">No results found</div>
</mdb-autocomplete>
import { Component } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import {
map,
startWith,
tap,
} from 'rxjs/operators';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
searchText = new Subject<string>();
results: Observable<any[]>;
notFound = false;
data: any[] = [
{ title: 'One', description: 'Lorem ipsum dolor sit, amet consectetur adipisicing elit' },
{ title: 'Two', description: 'Lorem ipsum dolor sit, amet consectetur adipisicing elit' },
{ title: 'Three', description: 'Lorem ipsum dolor sit, amet consectetur adipisicing elit' },
{ title: 'Four', description: 'Lorem ipsum dolor sit, amet consectetur adipisicing elit' },
{ title: 'Five', description: 'Lorem ipsum dolor sit, amet consectetur adipisicing elit' },
];
constructor() {
this.results = this.searchText.pipe(
startWith(''),
map((value: string) => this.filter(value)),
tap((results: string[]) =>
results.length > 0 ? (this.notFound = false) : (this.notFound = true)
)
);
}
filter(value: string): string[] {
const filterValue = value.toLowerCase();
return this.data.filter((item: any) => item.title.toLowerCase().includes(filterValue));
}
displayValue(value: any): string {
return value ? value.title : '';
}
}
Asynchronous search
The function passed to the filter
option can return a Promise
that
resolves to an array of results. By default the component expects to receive data as an array
of strings. If you want to return an array of objects instead, you can use
displayValue
option to specify which parameter should be used as a display value
of the specific result.
<mdb-form-control style="width: 22rem">
<input
mdbInput
[ngModel]="searchText | async"
(ngModelChange)="searchText.next($event)"
[mdbAutocomplete]="autocomplete"
type="text"
id="autocomplete"
class="form-control"
/>
<label mdbLabel class="form-label" for="autocomplete">Example label</label>
<div
*ngIf="loading"
class="autocomplete-loader spinner-border"
role="status"
></div>
</mdb-form-control>
<mdb-autocomplete
#autocomplete="mdbAutocomplete"
[displayValue]="displayValue"
(opened)="onOpen()"
>
<mdb-option *ngFor="let option of results | async" [value]="option">
{{ option.name }}
</mdb-option>
<div *ngIf="notFound" class="autocomplete-no-results">No results found</div>
</mdb-autocomplete>
import { HttpClient } from '@angular/common/http';
import { Component } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { map, tap, debounceTime, delay, switchMap } from 'rxjs/operators';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
searchText = new Subject<string>();
results: Observable<any>
notFound = false;
loading = false;
dataLoaded = false;
constructor(private http: HttpClient) {
this.results = this.searchText.pipe(
debounceTime(250),
switchMap((value: string) => this.filter(value)),
tap((results: any) =>
results.length > 0 ? (this.notFound = false) : (this.notFound = true)
)
);
}
filter(value: string): any {
const filterValue = value.toLowerCase();
const url = `https://swapi.py4e.com/api/people/?search=${encodeURI(filterValue)}`;
this.loading = true;
return this.http.get(url).pipe(
map((data: any) => {
return data.results
}),
delay(300),
tap(() => (this.loading = false))
);
}
displayValue(value: any): string {
return value ? value.name : '';
}
onOpen(): void {
if (!this.dataLoaded) {
this.searchText.next('');
this.dataLoaded = true;
}
}
}
Threshold
Use threshold
option to specify a minimum number of the characters in the input
field needed to perform a search operation.
<mdb-form-control style="width: 22rem">
<input
mdbInput
[ngModel]="searchText | async"
(ngModelChange)="searchText.next($event)"
[mdbAutocomplete]="autocomplete"
type="text"
id="autocomplete"
class="form-control"
/>
<label mdbLabel class="form-label" for="autocomplete">Example label</label>
</mdb-form-control>
<mdb-autocomplete #autocomplete="mdbAutocomplete">
<mdb-option *ngFor="let option of results | async" [value]="option">
{{ option }}
</mdb-option>
<div *ngIf="notFound" class="autocomplete-no-results">No results found</div>
</mdb-autocomplete>
import { Component } from '@angular/core';
import { Observable, of, Subject } from 'rxjs';
import {
map,
mergeMap
} from 'rxjs/operators';
import { combineLatest } from 'rxjs'
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
searchText = new Subject<string>();
results: Observable<string[]>;
notFound = false;
data = ['One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight'];
constructor() {
this.results = this.searchText.pipe(
mergeMap((value: string) => {
const results = this.filter(value);
return combineLatest([of(value), of(results)]);
}),
map(([value, results]) => {
if (value.length > 2 && results.length === 0) {
this.notFound = true;
return results;
} else {
this.notFound = false;
}
return results;
})
);
}
filter(value: string): string[] {
const filterValue = value.toLowerCase();
if (filterValue.length >= 2) {
return this.data.filter((item: string) => item.toLowerCase().includes(filterValue));
} else {
return [];
}
}
}
Custom item template
It is possible to customize the appearance of the autocomplete option. You can
use the listHeight
option to modify the result list height when you want to
display more content in the component dropdown.
<mdb-form-control style="width: 22rem">
<input
mdbInput
[ngModel]="searchText | async"
(ngModelChange)="searchText.next($event)"
[mdbAutocomplete]="autocomplete"
type="text"
id="autocomplete"
class="form-control"
/>
<label mdbLabel class="form-label" for="autocomplete">Example label</label>
</mdb-form-control>
<mdb-autocomplete
#autocomplete="mdbAutocomplete"
[displayValue]="displayValue"
[listHeight]="290"
[optionHeight]="58">
<mdb-option *ngFor="let option of results | async" [value]="option">
<div class="autocomplete-custom-item-content">
<div class="autocomplete-custom-item-title">{{ option.title }}</div>
<div class="autocomplete-custom-item-subtitle">{{ option.subtitle }}</div>
</div>
</mdb-option>
<div *ngIf="notFound" class="autocomplete-no-results">No results found</div>
</mdb-autocomplete>
import { Component } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import {
map,
startWith,
tap,
} from 'rxjs/operators';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
searchText = new Subject<string>();
results: Observable<any[]>;
notFound = false;
data: any[] = [
{ title: 'One', subtitle: 'Secondary text' },
{ title: 'Two', subtitle: 'Secondary text' },
{ title: 'Three', subtitle: 'Secondary text' },
{ title: 'Four', subtitle: 'Secondary text' },
{ title: 'Five', subtitle: 'Secondary text' },
{ title: 'Six', subtitle: 'Secondary text' },
];
constructor() {
this.results = this.searchText.pipe(
startWith(''),
map((value: string) => this.filter(value)),
tap((results: string[]) =>
results.length > 0 ? (this.notFound = false) : (this.notFound = true)
)
);
}
filter(value: string): string[] {
const filterValue = value.toLowerCase();
return this.data.filter((item: any) => item.title.toLowerCase().includes(filterValue));
}
displayValue(value: any): string {
return value ? value.title : '';
}
}
.autocomplete-custom-item-content {
display: flex;
flex-direction: column;
}
.autocomplete-custom-item-title {
font-weight: 500;
}
.autocomplete-custom-item-subtitle {
font-size: 0.8rem;
}
Custom content
A custom content container with a class .autocomplete-custom-content will be displayed at the end of the autocomplete-custom-item-subtitle dropdown. You can use it to display a number of search results.
<mdb-form-control style="width: 22rem">
<input
mdbInput
[ngModel]="searchText | async"
(ngModelChange)="searchText.next($event)"
[mdbAutocomplete]="autocomplete"
type="text"
id="autocomplete"
class="form-control"
/>
<label mdbLabel class="form-label" for="autocomplete">Example label</label>
</mdb-form-control>
<mdb-autocomplete #autocomplete="mdbAutocomplete">
<mdb-option *ngFor="let option of results | async" [value]="option">
{{ option }}
</mdb-option>
<div *ngIf="notFound" class="autocomplete-no-results">No results found</div>
<div class="autocomplete-custom-content">Search results: {{ customContentLength }}</div>
</mdb-autocomplete>
import { Component } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import {
map,
startWith,
tap,
} from 'rxjs/operators';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
searchText = new Subject<string>();
results: Observable<string[]>;
notFound = false;
customContentLength: number | null = null;
data = ['One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight'];
constructor() {
this.results = this.searchText.pipe(
startWith(''),
map((value: string) => this.filter(value)),
tap((results: string[]) => {
results.length > 0 ? (this.notFound = false) : (this.notFound = true)
this.customContentLength = results.length;
})
);
}
filter(value: string): string[] {
const filterValue = value.toLowerCase();
return this.data.filter((item: string) => item.toLowerCase().includes(filterValue));
}
}
Reactive forms
<mdb-form-control style="width: 22rem">
<input
mdbInput
[ngModel]="searchText | async"
(ngModelChange)="searchText.next($event)"
[mdbAutocomplete]="autocomplete"
type="text"
id="autocomplete"
class="form-control"
/>
<label mdbLabel class="form-label" for="autocomplete">Example label</label>
</mdb-form-control>
<mdb-autocomplete #autocomplete="mdbAutocomplete">
<mdb-option *ngFor="let option of results | async" [value]="option">
{{ option }}
</mdb-option>
<div *ngIf="notFound" class="autocomplete-no-results">No results found</div>
</mdb-autocomplete>
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
import { Observable } from 'rxjs';
import {
map,
startWith,
tap,
} from 'rxjs/operators';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
reactiveControl = new FormControl();
results: Observable<string[]>;
notFound = false;
data = ['One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight'];
constructor() {
this.results = this.reactiveControl.valueChanges.pipe(
startWith(''),
map((value: string) => this.filter(value)),
tap((results: string[]) =>
results.length > 0 ? (this.notFound = false) : (this.notFound = true)
)
);
}
filter(value: string): string[] {
const filterValue = value.toLowerCase();
return this.data.filter((item: string) => item.toLowerCase().includes(filterValue));
}
}
Validation
<form [formGroup]="validationFormGroup" style="width: 22rem">
<mdb-form-control style="width: 22rem">
<input
mdbInput
mdbValidate
formControlName="validationControl"
[mdbAutocomplete]="autocomplete"
type="text"
id="autocomplete"
class="form-control"
/>
<label mdbLabel class="form-label" for="autocomplete">Example label</label>
<mdb-error *ngIf="input?.invalid && (input?.dirty || input?.touched)"
>Input value is required</mdb-error
>
<mdb-success *ngIf="input?.valid && (input?.dirty || input?.touched)"
>Looks good!</mdb-success
>
</mdb-form-control>
<mdb-autocomplete #autocomplete="mdbAutocomplete">
<mdb-option *ngFor="let option of results | async" [value]="option">
{{ option }}
</mdb-option>
<div *ngIf="notFound" class="autocomplete-no-results">No results found</div>
</mdb-autocomplete>
<button type="submit" id="submit" class="btn btn-primary btn-sm mt-5">Submit</button>
</form>
import { Component } from '@angular/core';
import { AbstractControl, FormControl, FormGroup, Validators } from '@angular/forms';
import { Observable } from 'rxjs';
import {
map,
startWith,
tap,
} from 'rxjs/operators';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
validationFormGroup: FormGroup;
results: Observable<string[]>;
notFound = false;
data = ['One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight'];
constructor() {
this.validationFormGroup = new FormGroup({
validationControl: new FormControl('', [Validators.required]),
});
this.results = this.input.valueChanges.pipe(
startWith(''),
map((value: string) => this.filter(value)),
tap((results: string[]) =>
results.length > 0 ? (this.notFound = false) : (this.notFound = true)
)
);
}
get input(): AbstractControl {
return this.validationFormGroup.get('validationControl')!;
}
filter(value: string): string[] {
const filterValue = value.toLowerCase();
return this.data.filter((item: string) => item.toLowerCase().includes(filterValue));
}
}
Autocomplete - API
Import
import { MdbAutocompleteModule } from 'mdb-angular-ui-kit/autocomplete';
…
@NgModule ({
...
imports: [MdbAutocompleteModule],
...
})
Inputs
Name | Type | Default | Description |
---|---|---|---|
displayValue
|
Function | (value) => value |
Function executed for complex search results, to get value to display in the results list ue |
optionHeight
|
number | 38 |
Changes the single option height value |
listHeight
|
number | 190 |
Height of the results list |
Outputs
Name | Type | Description |
---|---|---|
closed
|
EventEmitter<any> | Event fired when the select is closed |
opened
|
EventEmitter<any> | Event fired when the select is opened |
selected
|
EventEmitter<any> | Event fired when the autocomplete item is selected |
<mdb-form-control style="width: 22rem">
<input
mdbInput
[ngModel]="searchText | async"
(ngModelChange)="searchText.next($event)"
[mdbAutocomplete]="autocomplete"
type="text"
id="autocomplete"
class="form-control"
/>
<label mdbLabel class="form-label" for="autocomplete">Example label</label>
</mdb-form-control>
<mdb-autocomplete #autocomplete="mdbAutocomplete" (opened)="onOpen()">
<mdb-option *ngFor="let option of results | async" [value]="option">
{{ option }}
</mdb-option>
<div *ngIf="notFound" class="autocomplete-no-results">No results found</div>
</mdb-autocomplete>
import { Component } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import {
map,
startWith,
tap,
} from 'rxjs/operators';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
searchText = new Subject<string>();
results: Observable<string[]>;
notFound = false;
data = ['One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight'];
constructor() {
this.results = this.searchText.pipe(
startWith(''),
map((value: string) => this.filter(value)),
tap((results: string[]) =>
results.length > 0 ? (this.notFound = false) : (this.notFound = true)
)
);
}
filter(value: string): string[] {
const filterValue = value.toLowerCase();
return this.data.filter((item: string) => item.toLowerCase().includes(filterValue));
}
onOpen(): void {
console.log('opened');
}
}