Debouncing Angular 2 Input Component

Debouncing Angular 2 Input Component

This post was written when Angular 2 beta0 was the current version.

Since Angular 2 is now beta, I decided to give it a try by rewriting an existing Angular 1 app to Angular 2. And by rewrite I mean to remove all the written JS code for Angular 1 and start over with Angular 2 (so nongUpgrade). After reading some parts of the docs and this little introduction to Rx, I had everything to start. Doing some necessary changes to my Gulp tasks, since TypeScript is involved now, I created the bootstrap code and the first glory AppComponent. So far, so good. The first view of my app is just some texts and links, so nothing special. But the second one has an input element. Wow. Nothing special about forms and inputs, but my Angular 1 code was:

<input ng-model="model.search" ng-model-options="{debounce: 500}" class="form-control" placeholder="Search">

Oh my lovely ng-model-options=”{debounce: 500}”. A quick look in the docs reveals that there is (currently) no equivalent for that in Angular 2. Some searching with my favorite search engine reveals this or that post with running discussions about how to throttle or debounce DOM events within the core of Angular 2. The last post even provides a solution. Unfortunately this doesn’t seem to work with beta0, since the mentioned toRx()  method is not present. So I came up with my own solution!

InputDebounceComponent

import {Component, Input, Output, ElementRef, EventEmitter} from 'angular2/core';
import {CORE_DIRECTIVES} from 'angular2/common';
import {Observable} from 'rxjs/Rx';

@Component({
    selector: 'input-debounce',
    template: '<input type="text" class="form-control" [placeholder]="placeholder" [(ngModel)]="inputValue">'
})
export class InputDebounceComponent {
    @Input() placeholder: string;
    @Input() delay: number = 300;
    @Output() value: EventEmitter = new EventEmitter();

    public inputValue: string;

    constructor(private elementRef: ElementRef) {
        const eventStream = Observable.fromEvent(elementRef.nativeElement, 'keyup')
            .map(() => this.inputValue)
            .debounceTime(this.delay)
            .distinctUntilChanged();

        eventStream.subscribe(input => this.value.emit(input));
    }
}

At first the necessary classes are imported, then a new component is defined. Since it’s only a little template, a inline template is ok. It defines a bound placeholder  and two-way bound ngModel (which will save its changes to inputValue property). The class InputDebounceComponent  itself defines two inputs, placeholder  and delay , and a single output, the value . The inputs allow to change the placeholder and the delay later, when the component is used.

The magic comes with the constructor and its definition. Via its parameter elementRef: ElementRef  Angular 2 injects the template’s element, which can be accessed by the nativeElement  property. Then an Observable is created from the nativeElement’s keyup event leading to every keyup emitting an event, which flows through our stream. This event is mapped to the current inputValue . Next step is to use debounceTime which:

Emits an item from the source Observable after a particular timespan has passed without the Observable omitting any other items. OR Ignores values from an observable sequence which are followed by another value within a computed debounced duration.

That said, the stream will wait 300 msecs (per default), before passing on the event.

Last but not least, distinctUntilChanged() is used, so it filters out all non changed input values. This is very useful, if the input field has the focus and the user presses a modifier key (like ctrl or alt), which would emit an event but will not change the written text. In this case, I don’t want the event to be emitted at all.

The stream is now set up, let’s subscribe to it with a very simple callback function. The callback function uses value‘s emit method to emit the current debounced input. Easy!

Usage

The usage of the component is rather easy:

<form ngForm="searchForm">
    <input-debounce delay="500" placeholder="Search..." (value)="searchChanged($event)"></input-debounce>
</form>
import {Component} from 'angular2/core';
import {CORE_DIRECTIVES, FORM_DIRECTIVES} from 'angular2/common';
import {InputDebounceComponent} from "../../inputDebounce";

@Component({
    selector: 'search-view',
    directives: [CORE_DIRECTIVES, FORM_DIRECTIVES, InputDebounceComponent],
    templateUrl: 'app/components/search/search.html'
})
export class SearchComponent {
    public searchChanged(value) {
        console.log(value);
        // Make cool HTTP requests
    }
}

Within the HTML (value)=”searchChanged($event)” is used to call the component’s searchChanged()  method. The variable $event  is a special variable, which will contain the value emitted from the InputDebounceComponent.