bs-typeahead is a text input with asynchronous suggestions and free text. A [provider] function supplies the suggestions for a settled term and receives an AbortSignal: when a newer term supersedes a request, the older one is aborted and its answer is never shown. Typing is debounced ([debounceMs], 200 ms) and gated on a minimum length ([minLength], 1). The value is the text, usable with [(ngModel)] or formControlName; (itemSelect) delivers the picked item and (termSubmit) the term on Enter. The provider may answer with a Promise or an Observable.
The component is generated from one framework-neutral source (a core driving TypeaheadMachine and one template) into the Angular, React, Vue and Lit fronts. With JavaScript disabled, the server still renders a real, named input that submits as free text.
This provider answers after 1.5 seconds. Type mi, then quickly mich: the first request is aborted (count below) and only the second answer appears.
Aborted requests: 0 · Selected: — · Submitted: —
<bs-typeahead
[provider]="search"
[debounceMs]="200"
[minLength]="2"
inputLabel="Artist"
(itemSelect)="onSelect($event.item)"
(termSubmit)="onSubmit($event)"
></bs-typeahead>
import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BsTypeaheadComponent, TypeaheadRequest } from '@mintplayer/ng-bootstrap/typeahead';
interface Artist { id: number; text: string }
@Component({
selector: 'my-typeahead-demo',
templateUrl: './my-typeahead-demo.component.html',
imports: [BsTypeaheadComponent],
})
export class MyTypeaheadDemoComponent {
constructor(private http: HttpClient) {}
// Called once per settled term. Returning an Observable is fine: it is
// unsubscribed (the HTTP request cancelled) when a newer term supersedes it.
search = (term: string, request: TypeaheadRequest) =>
this.http.get<Artist[]>('/api/artists', { params: { q: term } });
onSelect(item: Artist) { console.log('selected', item); }
onSubmit(term: string) { console.log('submit', term); }
}
*bsTypeaheadItem renders one suggestion and receives the current term, here used to highlight the match. An in-memory provider with [debounceMs]="0" answers instantly.
<bs-typeahead [provider]="search">
<ng-container *bsTypeaheadItem="let artist = item; let term = term">
<strong>{{ artist.text }}</strong> <small class="text-secondary">{{ artist.genre }}</small>
</ng-container>
</bs-typeahead>
The control's value is the text, so free text is a valid value. A pick writes the item's text; the item itself arrives with (itemSelect).
Model: —
<!-- The value is the text; a pick writes the item's text, and (itemSelect) delivers the item. -->
<bs-typeahead #street="ngModel" name="street" [(ngModel)]="model.street" [provider]="streets" required></bs-typeahead>
<!-- Reactive forms work the same way: formControlName="street" -->
| Key | Action |
|---|---|
| ↓ / ↑ | Open the list; move the highlighted suggestion (focus stays in the input) |
| Home / End | First / last suggestion |
| Enter | Pick the highlighted suggestion, or submit the text when none is highlighted |
| Esc | Close the list |
| Tab | Close the list and move on |
Template-driven [(ngModel)] and reactive [formControl] both bind through the component's ControlValueAccessor: the value, the disabled state, and touched when focus leaves.
""""
<!-- template-driven (FormsModule) -->
<bs-typeahead [(ngModel)]="value" [provider]="instant" [debounceMs]="0" inputLabel="Artist"></bs-typeahead>
<!-- reactive (ReactiveFormsModule): control = new FormControl(...) -->
<bs-typeahead [formControl]="control" [provider]="instant" [debounceMs]="0" inputLabel="Artist"></bs-typeahead>