Rob Lyndon Rob Lyndon - 1 month ago 20
C# Question

ui.bootstrap.typeahead: how to combine $http with debounce

I'd like to take advantage of ui.bootstrap.typeahead because it is excellent. I'm implementing a search of a database that could contain millions of users, so I would really like to be able to debounce keystrokes in the search box before making a call to $http. Otherwise every keystroke will result in a search, and early keystrokes will generate slower searches than later keystrokes, resulting in a clunky user experience.

My current effort, which doesn't work, looks like this:

JavaScript:

angular.module("mycontrollers").controller("myCtrl", [
"$scope", "$http", "rx", "localisationService", "close", function ($scope, $http, rx, localisationService, close) {
var culture = localisationService.getCulture();
function getUsersObservable(val) {
return rx.Observable
.fromPromise($http.get("/api/" + culture + "/usersearch", { params: { userName: val } }))
.map(function (response) {
return response.data;
});
}
$scope.close = close;
$scope.$createObservableFunction("getUsers")
.debounce(400)
.filter(function (val) {
return val.length > 0;
})
.flatMapLatest(getUsersObservable)
.subscribe();
}
]);


HTML:

<div class="form-group">
<label for="the-user" localised-text-key="TheUser"></label>
<input type="text" id="the-user" ng-model="userId" uib-typeahead="user for user in getUsers($viewValue)" typeahead-loading="loadingUsers" class="form-control" />
</div>


Server Side:

public async Task<IHttpActionResult> Get(string userName)
{
var users = await _modelContext.Users.Where(u => u.UserName.Contains(userName)).OrderBy(u => u.UserName).Select(u => u.UserName).Take(20).ToArrayAsync();
return Ok(users);
}


The input is being debounced correctly; the
rx.observable
at the start of the JavaScript is returning the search results as an array of strings, and debouncing the input correctly. What I'm not sure how to do is to package the results up into a promise that can be interpreted correctly by ui.bootstrap.typeahead.

Answer

Ok, I totally missed it in the docs

ng-model-options $ - Options for ng-model (see ng-model-options directive). Currently supports the debounce and getterSetter options.

So the directive allows you to attach options to it's ng-model very much as plain ol' Angular does.

So you can then use it to set a debouce to your model value, and then call a function through the ng-change directive.

<input type="text" id="the-user" 
    ng-model="userId" 
    ng-model-options="{'debounce': 500}" 
    ng-change="sendHttpAfterDebounce()"
    uib-typeahead="user for user in getUsers($viewValue)" typeahead- 
    loading="loadingUsers" 
    class="form-control" />

Now your function (sendHttpAfterDebounce) will run 500 milliseconds after you're done typing.