Search code examples
javascriptangularjstwitter-bootstrapbootstrap-typeahead

Could not load value into angular-bootstrap typeahead values with resources


I have a problem with angular-ui typeahead component. It does not show values populated by angular resources, however using $http works well. I suppose I missing some trick here with asycn call and correct population of returned values.

Working code

$scope.searchForContact = function(val) {
  return $http.get('/api/contacts/search', {
    params: {
    q: val
  }
}).then(function(response){
  return response.data.map(function(item){
    return item.name;
  });
});

};

Not working code

$scope.searchForContact = function(val) {
  return Contact.search({q: val}, function(response){
    return response.map(function(item){
      return item.name;
    });
  });
});

...

'use strict';

app.factory("Contact", function($resource, $http) {
var resource = $resource("/api/contacts/:id", { id: "@_id" },
{
  'create':  { method: 'POST' },
  'index':   { method: 'GET', isArray: true },
  'search':  { method: 'GET', isArray: true, url: '/api/contacts/search', params: true },
  'show':    { method: 'GET', isArray: false },
  'update':  { method: 'PUT' },
  'destroy': { method: 'DELETE' }
}
);

 return resource;
});

Pug template code

input.form-control(
  type='text'
  ng-model='asyncSelected'
  uib-typeahead='contact for contact in searchForContact($viewValue)'
  typeahead-loading='loadingLocations'
  typeahead-no-results='noResults'
)
i.glyphicon.glyphicon-refresh(ng-show='loadingLocations')
div(ng-show='noResults')
  i.glyphicon.glyphicon-remove
  |
  |No Results Found

Angular resources are working fine, including search endpoint - I just output on page result returned by the search endpoint. In both results should be just an array with string values. What am I doing wrong?


Solution

  • The difference between $http.get and your Contact.search is that the first one returns a promise and the latter doesn't. Any $resource method will usually be resolved to the actual response. I'll show that with an example.

    Getting data with $http

    var httpResult = $http.get('http://some.url/someResource').then(function(response) {
        return response.map(function(item) { return item.name });
    });
    

    The httpResult object contains a promise, so we need to use then method to get the actual data. Moreover, the promise will be resolved to the mapped array, which is the expected result.

    Getting data with $resource

    var someResource = $resource('http://some.url/someResource');
    var resourceResult = someResource.query(function(response) {
        return response.map(function(item) { return item.name });
    });
    

    The resourceResult isn't a promise here. It's a $resource object which will contain the actual data after the response comes from the server (in short, resourceResult will be the array of contacts - the original, not mapped, even though there is a map function). However, the $resource object contains a $promise property which is a promise similar to one returned by $http.get. It might be useful in this case.


    Solution

    I read in documentation that in order to make uib-typehead work properly, the $scope.searchForContact needs to return a promise. Instead of passing the callback function to search, I would simply chain it with the $promise from $resource object to make it work.

    $scope.searchForContact = function(val) {
      return Contact.search({q: val}).$promise.then(function(response){
        return response.map(function(item){
          return item.name;
        });
      });
    });
    

    Let me know if it works for you.