Home > OS >  Angular Material autocomplete should call API each time and display suggestions
Angular Material autocomplete should call API each time and display suggestions

Time:09-18

I need to call api after typing two letters in input field and it should show me the dropdown, If I type one more letter, it should call another API. I'm having issue in binding and making dynamic API. As of now searchTerm is foo only. I need to change searchTerm each time according to input field

service.ts file:

import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Inject, Injectable } from '@angular/core';
import { Client, CLIENT_TOKEN } from 'projects/vhap/src/public-api';
import { BehaviorSubject, Observable, of, throwError } from 'rxjs';
import { delay, distinctUntilChanged } from 'rxjs/operators';
import { Item, Items } from './model';

const httpOptions =  {
  headers: new HttpHeaders({
    'Content-Type': 'application/json',
    Authorization: 'sometoken'
  })
}

@Injectable({ providedIn: 'root' })
export class BetaService {
  private readonly searchingsubject = new BehaviorSubject<boolean>(false);
  private apiUrl = 'eval/api/Test/GetSearchAutoSuggests'
  /**
   *
   */
  readonly searching$ = this.searchingsubject
    .asObservable()
    .pipe(distinctUntilChanged());

  constructor(
    private readonly http: HttpClient,
    @Inject(CLIENT_TOKEN) private readonly client: Client
  ) {}

 

  /**
   *
   * @param searchTerm
   * @param count
   * @returns
   */
  public getSearchAutoSuggests(
    searchTerm: string,
    count: number
  ): Observable<Items> {
    const url = `${this.apiUrl}?searchTerm=${searchTerm}&count=${count}`
    return this.http.get<Items>(url, httpOptions);
  }
 
}

ts file: //not sure how to use switchMap and bind FormControl

import { Component, OnInit, EventEmitter, Output } from '@angular/core';
import { BetaService } from '../beta.service';
import { FormControl } from '@angular/forms';
import { tap, startWith, debounceTime, distinctUntilChanged, switchMap, map } from 'rxjs/operators';
import { Observable, of, Subscription } from 'rxjs';

@Component({
  selector: 'app-find',
  templateUrl: './find.component.html',
  styleUrls: ['./find.component.scss']
})
export class FindComponent implements OnInit {
myControl = new FormControl();
beta: BetaService;
searchTerm = 'foo';
count = 10;
newData: any = [];
filteredOptions: Observable<any[]>;

  constructor(private betaService: BetaService) {
    this.beta = betaService;
    this.filteredOptions = this.myControl.valueChanges.pipe(
      startWith(''),
      debounceTime(400),
      distinctUntilChanged(),
  
      switchMap(() => {
       
      }) 
    )
   }

  ngOnInit(): void {

  }
  
  showConfig() {
    this.betaService.getSearchAutoSuggests(this.searchTerm, this.count)
    .subscribe((data) => {
      let item  = data.Items;
      item.map((val) => {
        this.newData.push(val);
        return this.newData
      })

    });

  }
}

//HTML File

<form >
    <mat-form-field >
      <input type="text" placeholder="Pick one" aria-label="Number" matInput [formControl]="myControl" [matAutocomplete]="auto">
      <mat-autocomplete #auto="matAutocomplete">
        <mat-option *ngFor="let option of newData | async" [value]="option">
          {{option}}
        </mat-option>
      </mat-autocomplete>
    </mat-form-field>
  </form>

CodePudding user response:

this.filteredOptions = this.myControl.valueChanges.pipe(
  debounceTime(400),
  //ignore search terms that have less than 2 characters, also handling stripping out whitepsaces
  filter( (searchTerm: string) => searchTerm.trim().length > 1) 
  distinctUntilChanged(),
  switchMap( (searchTerm: string) => {
    // now we switch observables to your service. i.e. "switchmap)
    // switchmap handles cancelling the previous pending request for the new one. ensuring the user doesn't see old data as they typehead
    return this.betaService.getSearchAutoSuggests(searchTerm, this.count)
  }) 
).subscribe( {
  next: (result: Items) => {
    // at this point, you have your 'items' array returned.
    // you can store it where you like
    this.newData = result;
  }
})

I observed that your template is using the | async pipe on the newData array. and this has a datatype of any. that is not recommended as it will cause readability issues in the long term.

with the changes above, the newData array is being updated based on the results of the http request for each keyword search, so you can get rid of the the | async pipe.

alternative, if you wanted to use the | async pipe to avoid managing subscriptions in the template, yould need to remove the subscription from your component and modify your template to be

<mat-option *ngFor="let option of filteredOptions | async" [value]="option">
  {{option}}
</mat-option>

but this is last note is an observation, not a requirement.

  • Related