How to use ngx-bootstrap typeahead with async httpclient call

pdaniels0013 picture pdaniels0013 · Jan 12, 2018 · Viewed 10.1k times · Source

I'm attempting to populate nxg-bootstrap typeahead with async results from a rest backend in Angular 4. Their site has an example (https://valor-software.com/ngx-bootstrap/#/typeahead) on how to do this with mock observable data, but I am struggling doing this with httpclient. All examples of people using this are using the old Http module instead of the new HttpClient module used in Angular 4.

This is their example:

import { Component } from '@angular/core';

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import { TypeaheadMatch } from 'ngx-bootstrap/typeahead';

@Component({
  selector: 'demo-typeahead-async',
  templateUrl: './async.html'
})
export class DemoTypeaheadAsyncComponent {
  asyncSelected: string;
  typeaheadLoading: boolean;
  typeaheadNoResults: boolean;
  dataSource: Observable<any>;
  statesComplex: any[] = [
    { id: 1, name: 'Alabama', region: 'South' },
    { id: 2, name: 'Alaska', region: 'West' },
    {
      id: 3,
      name: 'Arizona',
      region: 'West'
    },
    { id: 4, name: 'Arkansas', region: 'South' },
    { id: 5, name: 'California', region: 'West' },
    { id: 6, name: 'Colorado', region: 'West' },
    { id: 7, name: 'Connecticut', region: 'Northeast' },
    { id: 8, name: 'Delaware', region: 'South' },
    { id: 9, name: 'Florida', region: 'South' },
    { id: 10, name: 'Georgia', region: 'South' },
    { id: 11, name: 'Hawaii', region: 'West' },
    { id: 12, name: 'Idaho', region: 'West' },
    { id: 13, name: 'Illinois', region: 'Midwest' },
    { id: 14, name: 'Indiana', region: 'Midwest' },
    { id: 15, name: 'Iowa', region: 'Midwest' },
    { id: 16, name: 'Kansas', region: 'Midwest' },
    { id: 17, name: 'Kentucky', region: 'South' },
    { id: 18, name: 'Louisiana', region: 'South' },
    { id: 19, name: 'Maine', region: 'Northeast' },
    { id: 21, name: 'Maryland', region: 'South' },
    { id: 22, name: 'Massachusetts', region: 'Northeast' },
    { id: 23, name: 'Michigan', region: 'Midwest' },
    { id: 24, name: 'Minnesota', region: 'Midwest' },
    { id: 25, name: 'Mississippi', region: 'South' },
    { id: 26, name: 'Missouri', region: 'Midwest' },
    { id: 27, name: 'Montana', region: 'West' },
    { id: 28, name: 'Nebraska', region: 'Midwest' },
    { id: 29, name: 'Nevada', region: 'West' },
    { id: 30, name: 'New Hampshire', region: 'Northeast' },
    { id: 31, name: 'New Jersey', region: 'Northeast' },
    { id: 32, name: 'New Mexico', region: 'West' },
    { id: 33, name: 'New York', region: 'Northeast' },
    { id: 34, name: 'North Dakota', region: 'Midwest' },
    { id: 35, name: 'North Carolina', region: 'South' },
    { id: 36, name: 'Ohio', region: 'Midwest' },
    { id: 37, name: 'Oklahoma', region: 'South' },
    { id: 38, name: 'Oregon', region: 'West' },
    { id: 39, name: 'Pennsylvania', region: 'Northeast' },
    { id: 40, name: 'Rhode Island', region: 'Northeast' },
    { id: 41, name: 'South Carolina', region: 'South' },
    { id: 42, name: 'South Dakota', region: 'Midwest' },
    { id: 43, name: 'Tennessee', region: 'South' },
    { id: 44, name: 'Texas', region: 'South' },
    { id: 45, name: 'Utah', region: 'West' },
    { id: 46, name: 'Vermont', region: 'Northeast' },
    { id: 47, name: 'Virginia', region: 'South' },
    { id: 48, name: 'Washington', region: 'South' },
    { id: 49, name: 'West Virginia', region: 'South' },
    { id: 50, name: 'Wisconsin', region: 'Midwest' },
    { id: 51, name: 'Wyoming', region: 'West' }
  ];

  constructor() {
    this.dataSource = Observable.create((observer: any) => {
      // Runs on every search
      observer.next(this.asyncSelected);
    }).mergeMap((token: string) => this.getStatesAsObservable(token));
  }

  getStatesAsObservable(token: string): Observable<any> {
    let query = new RegExp(token, 'ig');

    return Observable.of(
      this.statesComplex.filter((state: any) => {
        return query.test(state.name);
      })
    );
  }

  changeTypeaheadLoading(e: boolean): void {
    this.typeaheadLoading = e;
  }

  changeTypeaheadNoResults(e: boolean): void {
    this.typeaheadNoResults = e;
  }

  typeaheadOnSelect(e: TypeaheadMatch): void {
    console.log('Selected value: ', e.value);
  }
}

With Template

<pre class="card card-block card-header">Model: {{asyncSelected | json}}
</pre>
  <input [(ngModel)]="asyncSelected"
         [typeahead]="dataSource"
         (typeaheadLoading)="changeTypeaheadLoading($event)"
         (typeaheadNoResults)="changeTypeaheadNoResults($event)"
         (typeaheadOnSelect)="typeaheadOnSelect($event)"
         [typeaheadOptionsLimit]="7"
         typeaheadOptionField="name"
         placeholder="Locations loaded with timeout"
         class="form-control">
  <div *ngIf="typeaheadLoading===true">Loading</div>
  <div *ngIf="typeaheadNoResults===true">&#10060; No Results Found</div>

Here is my attempt at the only part I can't get working:

this.dataSource = Observable.create((observer: any) => {
    // Runs on every search
    observer.next(this.typeAheadResult);
    }).mergeMap((token: string) => {  
        return this.httpClient.get<string[]>(`${this.typeAheadUrl}?q=${token}`);              
    });

Normally, for a call like this I'd do something like:

this.httpClient.get<string[]>(`${this.typeAheadUrl}?q=${token}`)
    .subscribe(results => this.results = results)

But this isn't quite correct

Answer

pdaniels0013 picture pdaniels0013 · Jan 12, 2018

Here is the solution, it probably has some poorly written or redundant code (especially with the .map(r => r)), that looks off to me, maybe it can be simplified, but to get async results back with HttpClient, this will work:

this.dataSource = Observable.create((observer: any) => {
    // Runs on every search
    observer.next(this.typeAheadResult);
    }).mergeMap((token: string) => {  
        return this.httpClient.get<string[]>(`${this.typeAheadUrl}?q=${token}`).map(r => r);              
    });