Typeahead

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.

Slow provider with cancellation

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); }
        
    
      
        }
        
    

Custom row template

*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>
        
    

Forms

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" -->
        
    

Keyboard

KeyAction
↓ / ↑Open the list; move the highlighted suggestion (focus stays in the input)
Home / EndFirst / last suggestion
EnterPick the highlighted suggestion, or submit the text when none is highlighted
EscClose the list
TabClose the list and move on

Angular forms

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

Value: ""
dirty: false · touched: false · disabled: false

Reactive

Value: ""
dirty: false · touched: false · disabled: false
  
    
      
        <!-- 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>