What is the proper way to implement async typeahead search?

Issue

In my Angular app, there is a page of “Listings”. Since there are hundreds of listings, the results only load 20 at a time and more are loaded when the user scrolls to the bottom and clicks More.

I want to put a search box above the listings that could send a more specific search term to the server to only return listings that match.

For example, when the user enters “myterm” into the search box, Angular should send a GET to https://myapp.com/listings/?search=myterm and display only those results on the “Listings” page.

I am not sure what the proper way to do this in Angular 2+ is. In AngularJS I would have simple fired a new request returning a promise everytime Search was pressed, but I suspect Angular2+ has a different approach. I just can’t seem to find any literature about it.

So far I’ve just got the initial function call in the component that calls the service with an empty search parameter (aka, get everything)

ngOnInit() {
  this.listings = this.listingService.getListings('');
}

Solution

I suggest using ReactiveFormsModule since it’ll be really easy to solve this issue.

First, let’s define our FormGroup, which will consist of a single form control.

form: FormGroup;

constructor(formBuilder: FormBuilder){
    this.form = formBuilder.group({
        search: ''
    });
}

Now that we’ve defined our simple form, what we’re interested in is sending a request as the user types, so basically whenever the value of our form control has changed. We can subscribe to the valueChanges Observable like this:

ngOnInit(){
    this.form.get('search').valueChanges
       .subscribe(value => this.listingService.getListings(value)));
}

This clearly works, but the problem with this is that we don’t want to send a request for every single character the user types. We want to wait until he’s done. We can fix that by using the debounceTime operator:

this.form.get('search').valueChanges
       .debounceTime(1000)
       .subscribe(value => this.listingService.getListings(value)));

We also use switchMap operator to avoid having to make a chain of subscriptions (since getListings also returns an observable). The last thing we need to do is add a filter because we don’t want to send a request with an empty string, assuming the user deletes everything typed. And we’ve got the filter operator for that:

this.form.get('search').valueChanges
       .filter(value => value)
       .debounceTime(1000)
       .switchMap(value => this.listingService.getListings(value))
       .subscribe(listings => this.listings = listings);

Template should be similar to this:

<form [formGroup]="form">
    <input type="text" formControlName="search">
</form>

Answered By – Christian

Answer Checked By – Dawn Plyler (AngularFixing Volunteer)

Leave a Reply

Your email address will not be published.