I am learning AngularJS and trying to implement Angular UI Bootstrap Typeahead in a simple search app using Elasticsearch. I want to use the $http service to load asynchronous results and have not worked much with js promises.
UPDATE: I've done some more learning and testing
I've include the Typeahead as a dependency in my app:
var searchApp = angular.module('searchApp', ['elasticsearch', 'ngSanitize', 'ui.bootstrap']);
Here is the js promise code with the ES Match query mapped to the content.autocomplete field:
this.getSuggestions = function(query) {
var deferred = $q.defer();
esClient.search({
index: 'bigtestindex',
body: {
"query": {
"match": {
"content.autocomplete": {
"query": query,
"operator": "and"
}
}
},
"suggest": {
"text": query,
"phraseSuggestion": {
"phrase": {
"field": "content",
"direct_generator": [{
"field": "content",
"suggest_mode": "popular",
"min_word_length": 3,
"prefix_length": 2
}]
}
}
},
"size": 5,
"_source": ["content.autocomplete"]
}
}).then(function(es_return) {
deferred.resolve(es_return);
}, function(error) {
deferred.reject(error);
});
return deferred.promise;
};
With this code, I am trying to achieve the asynchronous results as shown here
Here is the relevant html to display possible matches:
<li ng-repeat="gram in autocomplete.edgengrams">
<p ng-mousedown="searchForSuggestion()"><small>Search Suggestions: —</small>{{gram.content}}</p>
</li>
$http.get() return a promise object already , there is no need to wrap the result in another promise object..
For your reference
https://docs.angularjs.org/api/ng/service/$http#get
Related
Need to poll a service based on result of the GET service in angular
I have a reporting service which has the response as follows
{url}/report/72?sortby=label1
response
While the report is being computed
{
"id": "72",
"status": "computing"
}
On successful completion success
{
"id": "72",
"status": "completed",
"data": {
"columns": ["uuid", "label1", "label3"],
"rows": [
["xyghj", 927, 955]
]
}
}
I need to retry the call till I get "completed" status in the response and then defer the result
Did something similar years ago. This is not tested, and my angularjs might be weak, but the idea is this:
function poll() {
service.checkPoll().then(function(payload) {
if (payload.status !== "completed") {
if (iShouldTryAgain) {
$timeout(function() {
poll();
}, 1000);
}
}
else {
getResults(payload.data);
}
}, function(error){
});
}
checkPoll() returns a promise, and is the result of your API call.
payload.status is what tells you if your report is ready or not.
This function will poll every second, but you should max out with iShouldTryAgain to make sure you don't sit there forever. Maybe some max value.
I have problem with geting a data from Restangular promise. I always get a promise instead of pure data in JSON.
This is response from my API
localhost:3000/api/meal
{
"status": "success",
"data": [
{
"meal_id": 4,
"meal_type_id": 2,
"description": "blahblah",
"price": "3.50",
"info": "120/120/20g",
"restaurant_id": 2
},
...
...
}
],
"message": "Retrieved ALL meals"
}
This is my config method for extracting data from response
RestangularProvider.addResponseInterceptor(function(data, operation, what, url, response, deferred) {
var extractedData;
// .. to look for getList operations
if (operation === 'getList') {
// .. and handle the data and meta data
return data.data;
} else {
extractedData = data.data;
}
return extractedData;
});
This is how I am trying to get data from my API
Restangular.all('meal').getList().then(function(meals) {
$scope.menu = meals; //meals.plain()
console.log($scope.menu);
});
but i always get this response
I need just JSON array from "data" field for using in my application.
Sry guys, after hours of research and debuging I found bug in my backend API in one specific select. Now Express.js, pg-promise and application works correctly.
I'm following along on the Angular JS Tutorial and I was wondering if there is an alternate approach to how I'm modifying it.
Currently, I am returning data with a factory that is defined as such:
angular.
module('core.card').
factory('Card', ['$resource',
function($resource){
return $resource('cards/:cardId.json', {}, {
query: {
method: 'GET',
params: {cardId: 'cards'},
isArray: true
}
});
}
]);
This is all good and working, as cards.json has all of the cards available and that's exactly what I want to return.
The method that they're describing, such as dealing with a RESTful service, assumes that there are multiple other specific JSON files that could get returned based on the route. I understand how to use that with an actual service, but let's say I wanted to alter the returned JSON data before it gets bound to my module so I don't have a bunch of extra data that I don't need?
Lets say /cards/foo.json contains something like this:
[{
"id": "foo",
"name": "Bar",
"img": "foobar.png",
"unnecessaryKey": "remove me"
}]
But where would I write a function that only returns:
[{
"id": "foo",
"name": "Bar",
"img": "foobar.png"
}]
Would I assign it in the same place where the query function is, such as:
...
return $resource('cards/:cardId.json', {}, {
query: {
method: 'GET',
params: {cardId: 'cards'},
isArray: true
},
alterReturnedData: {
// doStuffToFormatData
}
});
...
Or would it be best to just modify it in my Component as I'm doing now?
function alterReturnedData(data){
// doStuffToFormatData
}
var unmodified = Card.get({cardId:'foo'}, function(){
self.data = alterReturnedData(unmodified);
});
I just feel like it'd be better to return the data from the Service I actually want to the Component Controller instead of having a lot of logic in there to skew it around.
Is my approach OK to run this function in the Controller?
Or is it best to alter it in the Service, and how would I do so?
Let's say I have a animals.json file with the following content:
{
"animals": {
"elephant": {
"size": "large"
},
"mouse": {
"size": "small"
}
}
}
And I'm adding that data to the scope of my controller:
animalsApp.controller('animalsCtrl', function($scope, $http){
$http.get('../../animals.json').success(function(data){
$scope.animals = data.animals;
});
});
Which works perfectly, however let's say I need to get some data from an API that I need to add to $scope.animals, which has the following data:
{
"animal_name": "Leonardo"
}
Which is returned when I go to the api with the jsons data:
http://api.someapi.com/animals/{animals.elepahant} // returns above json
Pretend {animals.elaphant} is the results I get when i loop my json, get a value from it, and get the data from a remote api with the query being a variable of mines, add results to my json and return that new modified json in $scope.animals.
So the final json would look like:
{
"animals": {
"elephant": {
"size": "large",
"name": "Leonardo"
},
"mouse": {
"size": "small",
"name": "Vader"
}
}
}
How can I achieve this?
Normally you would have an array of animals and loop through that array.
Since you have an object the principle is the same. It is important to use a closure for the loop. Will use angular.forEach() to create that closure.
Using for loops by themselves do not create a closure and can be problematic since the loop finishes long before the request responses are returned
$http.get('../../animals.json').success(function(data){
$scope.animals = data.animals;
angular.forEach(data.animals, function(v, animal_type){
var url = 'http://api.someapi.com/animals/' + animal_type;
$http.get(url).success(function(resp){
v.name = resp.animal_name;
});
});
});
There are also alternative ways to write this using chained promises.
I kept it simple for now
I have a AngularJS-based frontend using restangular to fetch records from a Django backend I've built.
I'm making a call for a client list with the following:
var app;
app = angular.module("myApp", ["restangular"]).config(function(RestangularProvider) {
RestangularProvider.setBaseUrl("http://172.16.91.149:8000/client/v1");
RestangularProvider.setResponseExtractor(function(response, operation) {
return response.objects;
});
return RestangularProvider.setRequestSuffix("/?callback=abc123");
});
angular.module("myApp").controller("MainCtrl", function($scope, Restangular) {
return $scope.client = Restangular.all("client").getList();
});
Chrome is showing the backend returning data with an HTTP 200:
abc123({
"meta": {
"limit": 20,
"next": "/client/v1/client/?callback=abc123&limit=20&offset=20",
"offset": 0,
"previous": null,
"total_count": 2
},
"objects": [{
"id": 1,
"name": "Test",
"resource_uri": "/client/v1/client/1/"
}, {
"id": 2,
"name": "Test 2",
"resource_uri": "/client/v1/client/2/"
}]
})
But once that happens I'm seeing the following stack trace appear in Chrome's console:
TypeError: Cannot set property 'route' of undefined
at restangularizeBase (http://172.16.91.149:9000/components/restangular/src/restangular.js:395:56)
at restangularizeCollection (http://172.16.91.149:9000/components/restangular/src/restangular.js:499:35)
at http://172.16.91.149:9000/components/restangular/src/restangular.js:556:44
at wrappedCallback (http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.js:6846:59)
at http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.js:6883:26
at Object.Scope.$eval (http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.js:8057:28)
at Object.Scope.$digest (http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.js:7922:25)
at Object.Scope.$apply (http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.js:8143:24)
at done (http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.js:9170:20)
at completeRequest (http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.js:9333:7) angular.js:5754
I did a breakpoint on line 395 in in restangular.js:
L394 function restangularizeBase(parent, elem, route) {
L395 elem[config.restangularFields.route] = route;
The first time it hits the breakpoint elem is just an object and route has the value of client.
The second time the breakpoint is hit elem is undefined and route has the value of client.
Any ideas why elem would be undefined the second time around?
When requesting lists, Restangular expects the data from the server to be a simple array. However, if the resulting data is wrapped with result metadata, such as pagination info, it falls apart.
If you are using Django REST Framework, it will return results wrapped like this:
{
"count": 2,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"name": "Foo"
},
{
"id": 2,
"name": "Bar"
}
]
}
To translate this, you need to create a response extractor function. It's easiest to specify in the module config:
angular.module('myApp', ['myApp.controllers', 'restangular']).
config(function(RestangularProvider) {
RestangularProvider.setBaseUrl("/api");
// This function is used to map the JSON data to something Restangular
// expects
RestangularProvider.setResponseExtractor(function(response, operation, what, url) {
if (operation === "getList") {
// Use results as the return type, and save the result metadata
// in _resultmeta
var newResponse = response.results;
newResponse._resultmeta = {
"count": response.count,
"next": response.next,
"previous": response.previous
};
return newResponse;
}
return response;
});
});
This rearranges the results to be a simple array, with an additional property of _resultmeta, containing the metadata. Restangular will do it's thing with the array, and it's objects, and you can access the _resultmeta property when handling the array as you would expect.
I'm the creator of Restangular.
The restangularizeBase function is called first for your collection and then for each of your elements.
From the StackTrace, the element is OK, but once the collection is sent to restangularizeBase, it's actually undefined. Could you please console.log response.objects? Also, please update to the latest version.
Also, for the default request parameter, you should be using defaultRequestParams instead of the requestSuffix. requestSuffix should only be used for the ending "/"
Let me know if I can help you some more!