Search code examples
angularangular-ui-typeahead

Using http with ngBootstrap Typeahead for Angular 4


I'd like to use ngBootstrap for Angular 4 Typeahead for autocomplete. The example they have for remote data retrieval is using Jsonp and not http. I've been trying to find some more info to replace Jsonp with http in that example. I'm not too familiar with Observables yet so I'm trying to learn them and get better understanding about them.

I've seen this example but it looks really simple and maybe(?) leaves out a lot... for the sake of simplicity?

Can someone point in the right direction, I'm trying to find some good examples using http with ngBootstrap Typeahead.

Edit

    {
  "took": 15,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "failed": 0
  },
  "hits": {
    "total": 9,
    "max_score": 4.2456956,
    "hits": [
      {
        "_index": "query-index",
        "_type": "autocomplete",
        "_id": "AVxqBE-3t2o4jx2g0ntb",
        "_score": 4.2456956,
        "_source": {
          "suggestions": "bruce"
        }
      },
      {
        "_index": "query-index",
        "_type": "autocomplete",
        "_id": "AVxqBE-3t2o4jx2g0ntc",
        "_score": 3.064434,
        "_source": {
          "suggestions": "bruce wayne"
        }
      },
      {
        "_index": "query-index",
        "_type": "autocomplete",
        "_id": "AVxqBE-3t2o4jx2g0ntd",
        "_score": 3.064434,
        "_source": {
          "suggestions": "bruce willis"
        }
      },

Edit 2

export class AutocompleteComponent {
  model: any;
  searching = false;
  searchFailed = false;

  constructor(private autocompleteService: Elasticsearch) {}

  //formatMatches = (query: any) => query.hits.hits._source.suggestions || '';
  //formatMatches = (query: any) => query.hits.hits || '';
  formatMatches = (query: any) => <any>response.json().hits.hits || '';
  search = (text$: Observable<string>) =>
  //search = (text$: Observable<Suggestion[]>) =>
    text$
      .debounceTime(300)
      .distinctUntilChanged()
      //.switchMap(term =>
      //Observable.fromPromise(this.autocompleteService.search(term)
      .switchMap(term =>
      this.autocompleteService.search(term)
      .do( () => this.searchFailed = false)
      .catch( () => {
        this.searchFailed = true;
        return Observable.of([]);
      }))
      .do( () => this.searching = false);
}

Solution

  • I think I know a little of how to explain it. However, I am building a modal that handles filters. Below is my httpService.getCarriers which takes a search string.

    getCarriers(query: string): Observable<any> {
      return this._http.get(this._ripcord + '/carriers?search_string=' + query, this.options)
        .map((response: Response) => <any>response.json().data)
        .do(data => console.log(data))
        .catch(this.handleError);
    }
    

    In my modal component (filters.component.ts) file, knowing that my service returns an array of objects, I had to create a formatter method to handle both the input as well as the results object structures.

    I figured that since the ngbdTypeahead accepts an Observable I would send the term to my httpservice and allow it to return an Observable instead of trying to subscribe to it.

    // filters.component.ts
    import { Component, OnInit } from '@angular/core';
    import { NgbModal, NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
    import { FormBuilder, FormGroup, Validators } from '@angular/forms';
    import { Observable } from 'rxjs/Observable';
    import 'rxjs/add/observable/of';
    import 'rxjs/add/operator/catch';
    import 'rxjs/add/operator/debounceTime';
    import 'rxjs/add/operator/distinctUntilChanged';
    import 'rxjs/add/operator/do';
    import 'rxjs/add/operator/map';
    import 'rxjs/add/operator/switchMap';
    
    import { HttpService } from '../../../shared/http.service';
    import { Carrier } from '../../../definitions/carrier';
    
    @Component({
      selector: 'afn-ngbd-modal-content',
      templateUrl: './modal/filters.modal.html',
      styleUrls: ['./modal/filters.modal.css']
    })
    export class NgbdModalContentComponent {
    
      filtersForm: FormGroup;
      carriers: Carrier[];
    
      constructor(public activeModal: NgbActiveModal, public httpService: HttpService, private fb: FormBuilder) {
        this.createForm();
      }
    
      carrier_search = (text$: Observable<string>) =>
        text$
          .debounceTime(200)
          .distinctUntilChanged()
          .do((text) => console.log(text))
          .switchMap(term =>
            this.httpService.getCarriers(term)
          )
      ;
    
      formatter = (x: {attributes: {name: string}}) => x.attributes.name;
    
      createForm() {
        this.filtersForm = this.fb.group({
          name: ['', Validators.required],
        });
      }
    }
    
    @Component({
      selector: 'afn-filters',
      templateUrl: './filters.component.html',
      styleUrls: ['./filters.component.css']
    })
    export class FiltersComponent implements OnInit {
    
      constructor(private modalService: NgbModal) { }
    
      open() {
        const modalRef = this.modalService.open(NgbdModalContentComponent);
      }
      ngOnInit() {
      }
    
    }
    

    Here is my HTML template for my modal:

    // filters.modal.html
    <div class="modal-header">
      <h4 class="modal-title">Hi there!</h4>
      <button type="button" class="close" aria-label="Close" (click)="activeModal.dismiss('Cross click')">
        <span aria-hidden="true">&times;</span>
      </button>
    </div>
    <div class="modal-body">
      <!--<p>Hello, {{name}}!</p>-->
      <form [formGroup]="filtersForm" novalidate>
        <div class="form-group">
          <label class="center-block">Carrier:
            <input type="text" class="form-control" formControlName="name" [ngbTypeahead]="carrier_search" [inputFormatter]="formatter" [resultFormatter]="formatter">
          </label>
        </div>
      </form>
    
      <p>Form value: {{ filtersForm.value | json }}</p>
      <p>Form status: {{ filtersForm.status | json }}</p>
    </div>
    <div class="modal-footer">
      <button type="button" class="btn btn-secondary" (click)="activeModal.close('Close click')">Close</button>
    </div>
    

    Let me know if there are any specific questions. I sort of hacked around until I got it working.

    Needless to say, even though debounceTime is awesome, I still don't want to execute a request until the user has typed a minimum of 3 characters. I get an error if I try and place that logic within the switchMap.

    enter image description here