Wednesday, June 29, 2016

Typeahead search with Angular2 and TypeScript


This blog describes typeahead search implementation using Angular 2 and TypeScript. The functionality allows user to start typing in a form field and provides list of recommendations with the given text. See my previous blogs for Angular 1.x implementation and Introduction to RxJS in Angular 2



Type ahead search: 

Off late, one of the common feature in forms (on a web page) is type ahead search. It is generally tied to text fields and drop-downs. The functionality allows user to start typing in a form field and it provides list of recommendations with the given text.

The implementation is relatively simple for fields like country list or states list. We can download the complete list in the browser and filter through while user is typing. However, if it is a stock ticker, we might need to retrieve data dynamically from a server API. Here, efficiently managing the server calls is critical.


Reactive Extensions - JavaScript : 

RxJS (Reactive Extensions JavaScript) and observables are quite effective with requirements like typeahead search.

RxJS is nothing but,
  1. Observables -  asynchronously process stream of data. The source of the stream could be network resources (Server APIs) or UI controls. Observable emits items in a stream and an observer processes them.
  2. Operators - that act on emitted sequence of items. Examples: 
      1. map() for transforming results
      2. take(n) for selecting first n items from an observable
  3. Scheduler - for scheduling and decision making on when to emit an item in an observable.

My previous two blogs(1 & 2) discussed RxJS and Observables. Type ahead search is even a better example for the following reasons and complexity
  • A continuous stream of events are emitted from the UI - user types characters in the text field resulting in new information each key press.
  • Potentially each key press can result in server side API call to retrieve new information. Handle results from the API calls asynchronously. 


Implementation: 

Consider following component code,

 @Component({
    selector: 'typeahead-search',
    templateUrl: 'app/templates/search.tpl.html'
 })
 export class AppComponent
 {
    searchForm: any;
    results: Observable<any>;  
 
    constructor(private fb: FormBuilder, private http: Http){
        this.searchForm = this.fb.group({
            'searchField': [''] // we can set default value - it is set to be an empty string here.
        });

        // reference the control (ngControl)
        var ctrl = this.searchForm.controls.searchField;



        this.results = ctrl.valueChanges
                    .switchMap(fieldValue => this.http.get(`http://localhost:3001/api/search?term=${fieldValue}`))
                    .map(res => res.json());

 }


We are creating an element/component named typeahead-search. We have a single text field in the form. Form Builder API helps create Control Group, set up validations etc. For simplicity, we are not adding any validations for the typeahead text field in the sample.

Consider following line of code. This line is at the heart of type ahead search. That's all it takes to implement typeahead search in Angular 2. 

        
this.results = ctrl.valueChanges
                    .switchMap(fieldValue =>          this.http.get(`http://localhost:3001/api/search?term=${fieldValue}`))
                    .map(res => res.json());

  • On the control (text field) valueChanges is an observable<any>. It emits new text as user is typing. It emits new text.
  • On the observable, the switchMap() operator accepts each value emitted and passes it on to the callback. The emitted values are latest search string on the control. The callback function makes an HTTP call using http.get API. 
  • The returned object is transformed to desired result using another RxJS operator map

Debounce: debounceTime(timeInMilliseconds)

It is another useful RxJS operator. It restricts emitting items from an observable only after the given timespan. It drops events till the given time and emits the latest. For example, if we use it on valueChanges observable in the above example, next value is emitted only after given milliseconds. Only the latest is emitted. That is, as user is typing text, if we set debounce to 500 milliseconds, newer text is returned (emitted) at 500 milliseconds interval (while user is typing). This reduces number of API calls to the server and makes the whole thing manageable. Consider following code and images


        this.results = ctrl.valueChanges

                    .debounceTime(500)
                    .switchMap(fieldValue => this.http.get(`http://localhost:3001/api/search?term=${fieldValue}`))
                    .map(res => res.json());




Code Sample

For the complete code sample, follow the link

References