7

I am trying to get Angular UI bootstraps typeahead working with a REST resource I have set up. But I am not sure how to get it working with it's asynchronous nature.

At the moment I have adapted the example given by Angular UI Bootstrap.

so my html looks like so, calling getLibs() to get the list for the typeahead dropdown.

<div class='container-fluid' ng-controller="TypeaheadCtrl">
    <pre>Model: {{selected| json}}</pre>
    <input type="text"  typeahead="lib.name for lib in getLibs($viewValue)" active='active' ng-model="selected" typeahead-min-length='3' >
</div>

my resource looks like so:

angular.module('LibraryLookupService', ['ngResource']).factory('Library', 
    function($resource){
        return $resource(   "../../api/seq/library/?name__icontains=:searchStr" , {} , 
        {   'get':    {method:'GET'},
            'query':  {method:'GET', params: {} ,  isArray:false },
        }
    )
}

);

my controller looks like so (I am guessing it is here I am doing something incorrect):

function TypeaheadCtrl($scope , Library){

    $scope.selected = undefined;
    $scope.active = undefined ;

    $scope.libs = [{name:'initial'} , {name:"values"}];

    $scope.getLibs = function(viewValue){
        console.log("value:", viewValue);
        if (viewValue.length > 3 ) { 
            var return_vals =  Library.query({searchStr:viewValue},  function() {
                $scope.libs = return_vals.objects ; 
                console.log("here", $scope.libs) ; 
                }) ;
            }
        return $scope.libs
    }
}

So as I understand it, the typeahead menu is being populated from the return value of the getLibs() function. When getLibs() is called it is querying the REST interface, but initially an empty value is returned. This is being populated by the function supplied to the Library.query() method, and this is done after the data is returned from the REST request.

This means basically that the menu is being updated one keypress later than what I want. I type '3456' and it gets populated with results of a '345' query to the REST interface.

How do I get the menu to update when the response is returned from the Library.query() function? Am I going about this correctly?

Dmonix
  • 1,155
  • 7
  • 13
wobbily_col
  • 11,390
  • 12
  • 62
  • 86

6 Answers6

7

I solved it this way:

My factory:

angular.module('frontendApp')
  .factory('PersonService', function($resource) {
    // Service logic
    // ...

    return $resource(APP_CONFIG.prefixURLRest + '/person', {
      //id: '@id'
    }, {
      search: {
        method: 'POST',
        isArray: true
      }
    });

  });

My controller

...
$scope.searchTypeAhead = function(name) {
  var person = {
    'name': name
  };

  return PersonService.search(person)
    .$promise.then(function(response) {
      return response;
    });
};
  • 7
    Actually you could just return the $promise without the then() method as it is redundant. – mameluc May 12 '15 at 09:13
  • I don't understand why you are passing in the person object when you call the PersonService as I don't see a Person object in the factory definition. – peztherez Dec 05 '15 at 16:08
6

The typeahead directive from http://angular-ui.github.io/bootstrap/ relays on the promise API ($q) to handle asynchronous suggestions. The trouble with $resource is that is used to lack support for promises (it was added only recently in the unstable branch). So you've got 2 solutions here:

1) relay on $http that works with promises (IMO $http should be more than enough for most auto-complete cases). 2) Switch to the latest unstable version of AngularJS and work with promises.

The purpose of the $resource is to work with the full set of HTTP verbs to target RESTful endpoints. If you want to just query data you might be better off using $http for this purpose anyway.

There is a similar question about typeahead with an answer that covers of its usage with $http: How to tie angular-ui's typeahead with a server via $http for server side optimization?

Community
  • 1
  • 1
pkozlowski.opensource
  • 117,202
  • 60
  • 326
  • 286
2

If you don't want to use $http (because you want to reuse your existing $resource), or don't want to switch to the unstable branch, you can wrap the call to your $resource in a promise. It's a bit verbose, but it works. Quick example (using AngularJS 1.0.6):

$scope.getLibs = function(input){
    var defer = $q.defer();
    if (input){
        Library.query({searchStr:viewValue},
            function(data,headers){
                if (!$scope.$$phase){ //check if digest is not in progress
                    $rootScope.$apply(function(){
                        defer.resolve(data);
                    });
                } else {
                    defer.resolve(data);
                }
            },
            function(response){
                if (!$scope.$$phase){
                    $rootScope.$apply(function(){
                        defer.reject('Server rejected with status ' + response.status);
                    });
                } else {
                    defer.reject('Server rejected with status ' + response.status);
                }
            });
    } else {
        if (!$scope.$$phase){
            $rootScope.$apply(function(){
                defer.reject('No search query ');
                $log.info('No search query provided');
            });
        } else {
            defer.reject('No search query ');
            $log.info('No search query provided');
        }
    }
    return defer.promise;
};
sgdesmet
  • 628
  • 6
  • 14
2

Just return $promise from resource:

UsuarioResource.query().$promise
Rafael Coutinho
  • 523
  • 5
  • 11
1

If you are on Angular 1.2.13 (or somewhere around there, haven't tested them all)

See: https://stackoverflow.com/a/21888107/484802

Worked for me.

Community
  • 1
  • 1
Andrew Lank
  • 1,607
  • 1
  • 15
  • 29
1

This code is working for angular 1.6 and latest ui's angular-bootstrap3-typeahead

api endpoint response for GET /cities?name="somename":

[
    {name: "somename", zipcode: 11111, etc: ""},
    {name: "somename", zipcode: 22222, etc: ""},
    {name: "somename", zipcode: 33333, etc: ""}
]

$resource factory:

getCities: {
    url: '/cities',
    method: 'GET
}

controller:

$scope.getCities = function(name) {
    return City.getCity({
        name: name
    }).$promise.then(function(response) {
        return response;
    });
};

view:

<input ng-model-control data-ng-model="city" uib-typeahead="city.name for city in getCities($viewValue)" />

It seems that this solution doesn't work with filtering, see https://github.com/angular-ui/bootstrap/issues/1740.

joe.js
  • 271
  • 1
  • 2
  • 12