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>
<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>
<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: any) => {
const title = typeof value === 'string' ? value : value.title;
return this.filter(title)
}),
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>
<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: any) => {
const name = typeof value === 'string' ? value : value.name;
return this.filter(name)
}),
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>
<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>
<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: any) => {
const title = typeof value === 'string' ? value : value.title;
return this.filter(title)
}),
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>
<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>
<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>
<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';
import { MdbFormsModule } from 'mdb-angular-ui-kit/forms';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
…
@NgModule ({
...
imports: [MdbAutocompleteModule, MdbFormsModule,FormsModule, ReactiveFormsModule],
...
})
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<void> | Event emitted when the dropdown has been closed |
opened
|
EventEmitter<void> | Event emitted when the dropdown has been opened |
selected
|
EventEmitter<MdbAutocompleteSelectedEvent> | Event emitted when the autocomplete's item has been selected |
<mdb-form-control>
<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()"
(selected)="onSelected($event)"
>
<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 { MdbAutocompleteSelectedEvent } from 'mdb-angular-ui-kit/autocomplete';
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');
}
onSelected(event: MdbAutocompleteSelectedEvent): void {
console.log('selected: ', event);
}
}
Advanced types
MdbAutocompleteSelectedEvent
interface MdbAutocompleteSelectedEvent {
component: MdbAutocompleteComponent;
option: MdbOptionComponent;
}
CSS variables
--#{$prefix}autocomplete-label-active-transform: #{$autocomplete-label-active-transform};
--#{$prefix}autocomplete-label-color: #{$autocomplete-label-color};
--#{$prefix}form-outline-select-notch-border-color: #{$form-outline-select-notch-border-color};
--#{$prefix}autocomplete-input-focused-color: #{$autocomplete-input-focused-color};
--#{$prefix}autocomplete-dropdown-container-zindex: #{$autocomplete-dropdown-container-zindex};
--#{$prefix}autocomplete-dropdown-background-color: #{$autocomplete-dropdown-background-color};
--#{$prefix}autocomplete-dropdown-box-shadow: #{$autocomplete-dropdown-box-shadow};
--#{$prefix}autocomplete-dropdown-margin: #{$autocomplete-dropdown-margin};
--#{$prefix}autocomplete-dropdown-transform: #{$autocomplete-dropdown-transform};
--#{$prefix}autocomplete-dropdown-transition: #{$autocomplete-dropdown-transition};
--#{$prefix}autocomplete-dropdown-open-transform: #{$autocomplete-dropdown-open-transform};
--#{$prefix}autocomplete-item-color: #{$autocomplete-item-color};
--#{$prefix}autocomplete-item-padding: #{$autocomplete-item-padding};
--#{$prefix}autocomplete-item-font-size: #{$autocomplete-item-font-size};
--#{$prefix}autocomplete-item-font-weight: #{$autocomplete-item-font-weight};
--#{$prefix}autocomplete-item-hover-background-color: #{$autocomplete-item-hover-background-color};
--#{$prefix}autocomplete-item-disabled-color: #{$autocomplete-item-disabled-color};
SCSS variables
$autocomplete-dropdown-container-zindex: 1065;
$autocomplete-label-max-width: 80%;
$autocomplete-label-active-transform: translateY(-1rem) translateY(0.1rem) scale(0.8);
$autocomplete-input-focused-color: #616161;
$autocomplete-label-color: $primary;
$autocomplete-dropdown-background-color: #fff;
$autocomplete-dropdown-box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
$autocomplete-dropdown-margin: 0;
$autocomplete-dropdown-transform: scaleY(0.8);
$autocomplete-dropdown-transition: all 0.2s;
$autocomplete-dropdown-open-transform: scaleY(1);
$autocomplete-item-color: rgba(0, 0, 0, 0.87);
$autocomplete-item-padding: 6.5px 16px;
$autocomplete-item-font-size: 1rem;
$autocomplete-item-font-weight: 400;
$autocomplete-item-hover-background-color: #ddd;
$autocomplete-item-disabled-color: #9e9e9e;