AngularJS: Unexpected undefined in chained results - javascript

I've come across this issue before with nested directives, but I managed to find a workaround there. I have code that looks a bit like,
var token = API.callGeneric({}, {method: 'kds.getTokenExtended2', params: ['demo', 'demo', '', '', '', '', '', false, '', '']}); //kds.
token.$promise
.then(function (result) {
if (!angular.isUndefined(result.error)) { // API error
$scope.msg = {iconClass: 'glyphicon-exclamation-sign', txt: 'Looks like there was a problem.'}
if (!APIErr.handle(result.error)) { // handle also returns continueExec flags
return;
}
}
$scope.msg = {iconClass: 'glyphicon-cloud-download', txt: 'almost there…'};
$scope.token = result.result;
console.log('result', result.result);
}, function (error) { // server error
$scope.msg = {iconClass: 'glyphicon-exclamation-sign', txt: 'issues with server, summoning the gods'}
APIErr.handle(error);
})
.then(function (result) {
$scope.msg = {}; // clear the message
// another api call to get bills
return API.callGeneric({}, {method: 'kds.getKitchenDisplayReceipts', params: [$scope.token, new Date().getTime()]});
}, APIErr.handle)
.then(function (result) {
console.log(result); // can see result.result.openReceipts
var receiptIds = result.result.openReceipts; // undefined?
}, APIErr.handle);
And API is a service that calls the API, obviously.
The problem is the last few lines, where console.log(result) shows result.result.openReceipts, and obviously result is a Resource object.
I'm stumped about what might be going on here. Any clues? How can I avoid this in future?

If you want to nest promises you need to return a promise every time.
Your second then is unnecessary in my opinion and could be done inside the first one as the first one is not returning any promises.
So it could be something like:
Pseudo-code:
API.call('token').then(function(result) {
...
return API.call('displayreceipts');
})
.then(function(result){
var recieptIds = result.result.openReceipts;
})
Let me know if it works.

Related

AngularJS - Correctly formatting asynchronous calls

newbie here.
I am trying to understand how I need to structure asynchronous calls within my controller to fit my specific use case:
Consider the following code snippet from an Angular Module in "service.js" within my project:
function getSearchObjects(projectName, title) {
var payload = JSON.stringify({
"title": title
});
var request = $http({
method: 'post',
url: URL + '/search/' + projectName,
data: payload
});
return request.then(handleSuccess, handleError);
};
function runQuery(projectName, fromDate, toDate, sort, direction, columns) {
var from = Date.parse(fromDate);
var to = Date.parse(toDate);
var payload = JSON.stringify({
"fromDate": from,
"toDate": to,
"sort": sort,
"direction": direction,
"columns": columns
});
console.log(payload);
var request = $http({
method: 'post',
url: URL + '/query/' + projectName,
data: payload
});
return request.then(handleSuccess, handleError);
}
function handleSuccess(response) {
return response.data;
};
function handleError(response) {
if (!angular.isObject( response.data ) || !response.data.error) {
return( $q.reject( "An unknown error occurred." ) );
}
return $q.reject( response.data.error );
};
});
Within my controller, I am trying to troubleshoot the following function:
$scope.submit = function() {
var objectProperties = exportsStorageService.getSearchObjects($scope.selected.project.name, $scope.selected.search)
.then(function(result) {
exportsStorageService.runQuery($scope.selected.project.name, $scope.selected.start_date, $scope.selected.end_date, objectProperties.sort, objectProperties.direction, objectProperties.columns)
},
function(error) {
console.log(error);
});
};
getSearchObjects matches a title ($scope.selected.search) selected in my UI and grabs the following more detailed object from an API call:
{ title: 'Duplication Example',
sort: '#_traac-timestamp',
direction: 'desc',
columns: [ '#_traac-remote_ip', 'c-platform-m-distinct-id_s', '_type' ] }
I am trying to grab the properties returned from getSearchObjects and pass them along with a few user selected values from my UI to runQuery, which then returns data from a database to the user, but when I check the values passed to runQuery using the above logic in my controller, I get the following values. All of the objectProperties values I am attempting to pass to runQuery are undefined:
project_name: "Example Project"
start_date: 1499770800000
end_date: 1499943600000
sort: undefined
direction: undefined
columns: undefined
I have been trying to debug this, but I am too new to using Angular and asynchronous calls to really understand what I am doing wrong. My best guess currently is that I am calling runQuery before the values retrieved from getSearchObjects are attached to objectProperties. Either that or I am incorrectly referencing the properties within the objectProperties variable.
Could someone help me troubleshoot this issue, and better understand what I am doing wrong?
Thank you in advance for your help!
When you do this:
var objectProperties = some async function...
You are assigning the promise of the async function to the variable, not the result of it.
The result is coming in the .then, like you declared:
.then(function(result) { ... }
So, instead of objectProperties.sort, objectProperties.direction, objectProperties.columns, try using result.sort, result.direction, result.columns :)
If you are new to Promises, take a look at this simple, but great tutorial.
EDIT
Based on your comment, you are receiving, inside the response.data, the following object:
{"objectMatch": {
"title": "doc-event",
"sort": "#_traac-timestam‌​p",
"direction": "desc‌​",
"columns": [
"m-doc-‌​name_s",
"m-user_s",
"‌​m-full-action-type_s‌​",
"m-event-action-de‌​scriptor_s"
]}
}
So you have: response > data > objectMatch > properties you want.
The response.data you are extracting on your handleSuccess function:
function handleSuccess(response) {
return response.data;
};
So here, your result is response.data, containing the property objectMatch.
$scope.submit = function() {
var objectProperties = exportsStorageService.getSearchObjects($scope.selected.project.name, $scope.selected.search)
.then(function(result) {
...
},
...
If all of that is correct, you should be able to access the values you want using result.objectMatch.<sort, direction or columns>, like:
exportsStorageService.runQuery($scope.selected.project.name, $scope.selected.start_date, $scope.selected.end_date,
result.objectMatch.sort, result.objectMatch.direction, result.objectMatch.columns)

Query first works but find does not

I am using angularjs and parse.com to query for three members with spotlight = true.
When i use first it will find one but when i use find with limit(3) it will find nothing. I have even removed the Limit(3) and still nothing. I have searched the internet, after trying a few things i found still result is zero.
app.controller('spotlightCtrl', function($scope, $q) {
$scope.sl = {};
$scope.slmemid = "";
Parse.initialize("xxx", "xxx");
Parse.serverURL = 'https://parseapi.back4app.com';
var ArticleDfd = $q.defer();
var members = Parse.Object.extend('members');
var query = new Parse.Query(members);
query.equalTo("spotlight", true);
query.first().then(function (data) {
//query.find().then(function (data) { -----this find does not return results.
ArticleDfd.resolve(data);
}, function (error) {
ArticleDfd.reject(data);
console.log(error);
});
ArticleDfd.promise
.then(function (article) {
$scope.sl = article.attributes;
$scope.slmemid = article.id;
console.log(article);
})
.catch(function (error) {
//do something with the error
console.log(error);
});
});
Still looking for a way to do this right.
I have found a work around. I use the skip() function and make three controllers.
app.controller('spotlightCtrl1', function($scope, $q) {.....
.....
query.equalTo("spotlight", true);
query.first().then(function (data) {
app.controller('spotlightCtrl2', function($scope, $q) {......
.....
query.equalTo("spotlight", true).skip(1);
query.first().then(function (data) {...
app.controller('spotlightCtrl3', function($scope, $q) {......
.....
query.equalTo("spotlight", true).skip(2);
query.first().then(function (data) {....
I think this will be slower. still want to know the right code??
After searching and asking questions to Stackoverflow and back4app.com I have found my own work around (all my questions received little feedback).
I used REST. There was a trickyness with the "greaterthan" (#gt) date. I found the working syntax on stackoverflow. Also "Just remember to use https://parseapi.back4app.com/ endpoint instead of https://api.parse.com/1/" from Davi Macedo that i came across in a search on Google Groups.
$http({
method: 'GET',
url: ' https://parseapi.back4app.com/classes/Events',
headers: {
'X-Parse-Application-Id' : 'xxxx',
'X-Parse-REST-API-Key' : 'xxxx',
'Content-Type' : 'application/json'
},
params: {
where: {
'Luncheon': true,
'eventDate':{'$gt': {
"__type":"Date", "iso": currentTime
}
}
},
order: 'eventDate',
limit: 2
}
}).then(function successCallback(response) {
// this callback will be called asynchronously
// when the response is available
$scope.events = response.data.results;
}, function errorCallback(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
console.log(response);
});
This has worked well and my query completely works to replace the broken find query.

Trouble chaining HTTP requests in Node/Javascript

I'm attempting to recreate a Python script I wrote in Node/js and I'm having trouble wrapping my head around the asynchronous/callback way of it all.
The script is fairly simple and uses two basic HTTP requests to eBay's API. The first request gets a list of resulting item ids, then the second request gets specific information on each item (description/listing information etc). In Python this was fairly simple and I used a simple loop. It wasn't fast by any means, but it worked.
In javascript, however, I'm struggling to get the same functionality. My code right now is as follows:
var ebay = require('ebay-api');
var params ={};
params.keywords = ["pS4"];
var pages = 2;
var perPage = 2;
ebay.paginateGetRequest({
serviceName: 'FindingService',
opType: 'findItemsAdvanced',
appId: '',
params: params,
pages: pages,
perPage: perPage,
parser: ebay.parseItemsFromResponse
},
function allItemsCallback(error,items){
if(error) throw error;
console.log('FOUND', items.length, 'items from', pages, 'pages');
for (var i=0; i<items.length; i++){
getSingle(items[i].itemId);
}
}
);
function getSingle(id){
console.log("hello");
ebay.ebayApiGetRequest({
'serviceName': 'Shopping',
'opType': 'GetSingleItem',
'appId': '',
params: {
'ItemId': id ,
'includeSelector': 'Description'
}
},
function(error, data) {
if (error) throw error;
console.dir(data); //single item data I want
}
);
}
This is one attempt of many, but I'm recieving "possible EventEmitter memory leak detected" warnings and it eventually breaks with a "Error:Bad 'ack' code undefined errorMessage? null". I'm fairly sure this just has to do with proper utilization of callbacks but I'm unsure how to properly go about it. Any answers or help would be greatly appreciated. I apologize if this isn't a good question, if so please let me know how to correctly go about asking.
Node.js's asynchronous event chain is built on callbacks. Rather than:
getSingle(items[i].itemId);
You'll need to write a callback into that function that executes once the parent function is complete:
getSingle(items[i].itemId, function(err, data) {
// now you can access the data
});
And because ebay.ebayApiGetRequest is a lengthy function, the callback that tells its parent function that it's done must be called after that completes, like so:
ebay.ebayApiGetRequest({
//
},
function(error, data) {
callback(error, data);
}
);
But of course, if the parent function getSingle doesn't support a callback, then it won't go anywhere. So you'll need to support a callback param there as well. Here's the full script, rewritten using the event-driven callback model:
var ebay = require('ebay-api');
var async = require('async');
var params = {};
params.keywords = ["pS4"];
var pages = 2;
var perPage = 2;
ebay.paginateGetRequest({
serviceName: 'FindingService',
opType: 'findItemsAdvanced',
appId: '',
params: params,
pages: pages,
perPage: perPage,
parser: ebay.parseItemsFromResponse
},
function allItemsCallback(error, items) {
if (error) throw error;
console.log('FOUND', items.length, 'items from', pages, 'pages');
async.each(items, function(item, callback) {
getSingle(item.itemId, function(err, data) {
callback(err, data);
});
}, function(err, results) {
// now results is an array of all the data objects
});
}
);
function getSingle(id, callback) {
console.log("hello");
ebay.ebayApiGetRequest({
'serviceName': 'Shopping',
'opType': 'GetSingleItem',
'appId': '',
params: {
'ItemId': id,
'includeSelector': 'Description'
}
},
function(error, data) {
if (error) throw error;
console.dir(data); //single item data I want
callback(error, data);
}
);
}

undefined is not a function using $resource [duplicate]

When trying to poll a custom method copies on an AngularJS Resource I get the following error at angular.js:10033: (The method copy works just fine.)
TypeError: undefined is not a function
at https://code.angularjs.org/1.3.0-beta.8/angular-resource.min.js:9:347
at Array.forEach (native)
at q (https://code.angularjs.org/1.3.0-beta.8/angular.min.js:7:280)
at q.then.p.$resolved (https://code.angularjs.org/1.3.0-beta.8/angular-resource.min.js:9:329)
at J (https://code.angularjs.org/1.3.0-beta.8/angular.min.js:101:5)
at J (https://code.angularjs.org/1.3.0-beta.8/angular.min.js:101:5)
at https://code.angularjs.org/1.3.0-beta.8/angular.min.js:102:173
at g.$eval (https://code.angularjs.org/1.3.0-beta.8/angular.min.js:113:138)
at g.$digest (https://code.angularjs.org/1.3.0-beta.8/angular.min.js:110:215)
at g.$apply (https://code.angularjs.org/1.3.0-beta.8/angular.min.js:113:468)
Angular.js 10016 - 10035:
function consoleLog(type) {
var console = $window.console || {},
logFn = console[type] || console.log || noop,
hasApply = false;
// Note: reading logFn.apply throws an error in IE11 in IE8 document mode.
// The reason behind this is that console.log has type "object" in IE8...
try {
hasApply = !!logFn.apply;
} catch (e) {}
if (hasApply) {
return function() {
var args = [];
forEach(arguments, function(arg) {
args.push(formatError(arg));
});
return logFn.apply(console, args); // This is line 10033 where the error gets thrown.
};
}
Simplified resource:
angular.module('vgm.content-pages')
.factory('ContentPage', function($resource, $http) {
return $resource('/api/content-page/:id', { id:'#page.id' }, {
copy: {
method: 'POST',
url: '/api/content-page/copy/:id'
},
copies: {
method: 'GET',
isArray: true,
url: '/api/content-page/copies/:id'
}
});
});
Simplified directive where I'm getting the error:
angular.module('vgm.genericForms')
.directive('vgmFormCopyPicker', function() {
return {
restrict: 'E',
replace: true,
templateUrl: '/static/common/generic-forms/widgets/view-copy-picker.html',
scope: {
resource: '=',
},
controller: function($scope, $element) {
$scope.pages = [];
$scope.loadCopies = function() {
$scope.resource.$copies()
.then(function(response) {
$scope.pages = response.data;
});
};
}
};
});
As soon as I run the loadCopies method, executing the line $scope.resource.$copies() throws the error above.
In the Chrome Inspector I see the call to my API is actually being done. But resolving the promise seems to throw some error...
How can I solve this error?
EDIT:
$scope.resource = ContentPage.get({id: $stateParams.id}).$promise
$scope.resource.$save() // Works
$scope.resource.$update() // Works
$scope.resource.$copy() // Works
$scope.resource.$copies() // Does not work!
Angular Resource is trying to overwrite my initial resource with an array of items. But the instance of Resource does not have a method push of course.
I found the answer:
A resource is supposed to represent the matched data object for the rest of its lifespan. If you want to fetch new data you should do so with a new object.
$scope.copies = ContentPage.copies()
The answer from Guido is correct but I didn't get it at the first time.
If you add a custom method to your Angular $resource and using isArray: true and expecting to get an Array of something from your WebService you probably want to store the response in an Array.
Therefore you shouldn't use the instance method like this:
var ap = new Ansprechpartner();
$scope.nameDuplicates = ap.$searchByName(...);
But use the resource directly:
$scope.nameDuplicates = Ansprechpartner.searchByName(...)
Using following Angular resource:
mod.factory('Ansprechpartner', ['$resource',
function ($resource) {
return $resource('/api/Ansprechpartner/:id',
{ id: '#ID' },
{
"update": { method: "PUT" },
"searchByName": { method: "GET", url: "/api/Ansprechpartner/searchByName/:name", isArray: true }
}
);
}
]);
I am using Mean.js and this plagued for a few hours. There is a built in $update but it was not working when I tried to apply it to an object returned by a $resource. In order to make it work I had to change how I was calling the resource to update it.
For example, with a Student module, I was returning it with a $resource into $scope.student. When I tried to update student.$update was returning this error. By modifying the call to be Students.update() it fixed the problem.
$scope.update = function() {
var student = $scope.student;
var result = Students.update(student);
if(result){
$scope.message = 'Success';
} else {
$scope.error =
'Sorry, something went wrong.';
}
};

Can I act on and then forward the results of a AngularJS $http call without using $q?

I have functions like the getData function below.
I understand that $http returns a promise. In my current set up I am using $q so that I can do some processing of the results and then return another promise:
var getData = function (controller) {
var defer = $q.defer();
$http.get('/api/' + controller + '/GetData')
.success(function (data) {
var dataPlus = [{ id: 0, name: '*' }].concat(data);
defer.resolve({
data: data,
dataPlus: dataPlus
});
})
.error(function (error) {
defer.reject({
data: error
});
});
return defer.promise;
}
Is there any way that I can do this without needing to use the AngularJS $q (or any other $q implementation) or is the code above the only way to do this? Note that I am not looking for a solution where I pass in an onSuccess and an onError to the getData as parameters.
Thanks
As you say $http.get already returns a promise. One of the best things about promises is that they compose nicely. Adding more success, then, or done simply runs them sequentially.
var getData = function (controller) {
return $http.get('/api/' + controller + '/GetData')
.success(function (data) {
var dataPlus = [{ id: 0, name: '*' }].concat(data);
return {
data: data,
dataPlus: dataPlus
};
})
.error(function (error) {
return {
data: error
};
});
}
This means that using getData(controller).then(function (obj) { console.log(obj) });, will print the object returned by your success handler.
If you want you can keep composing it, adding more functionality. Lets say you want to always log results and errors.
var loggingGetData = getData(controller).then(function (obj) {
console.log(obj);
return obj;
}, function (err) {
console.log(err);
return err;
});
You can then use your logging getData like so:
loggingGetData(controller).then(function (obj) {
var data = obj.data;
var dataPlus = obj.dataPlus;
// do stuff with the results from the http request
});
If the $http request resolves, the result will first go through your initial success handler, and then through the logging one, finally ending up in the final function here.
If it does not resolve, it will go through the initial error handler to the error handler defined by loggingGetData and print to console. You could keep adding promises this way and build really advanced stuff.
You can try:
Using an interceptor which provides the response method. However I don't like it, as it moves the code handling the response to another place, making it harder to understand and debug the code.
Using $q would be the best in that case IMO.
Another (better ?) option is locally augmented transformResponse transformer for the $http.get() call, and just return the $http promise.

Categories