AngularJS: dependent asynchronous requests - javascript

New to AngularJS, wondering What would be the correct way to handle three asynchronous requests where the parameters of the third are defined by the responses of the previous two.
two addresses are passed to this function
$scope.getRoutes = function(origin, destination) {
//1.A request is made to get the coordinates of the first address
dataFactory.geocode(origin)
.success(function(data) {
$scope.originObj = data;
})
.error(function () {
});
//2.Same for the second address
dataFactory.geocode(destination)
.success(function(data) {
$scope.destinationObj = data;
})
.error(function () {
});
//3.Finally a request for transport routes between the two sets of coordinates
dataFactory.getRoutes($scope.originObj.coordinates, $scope.destinationObj.coordinates)
.success(function(data) {
$scope.routes = data;
})
.error(function () {
});
};
This gives me the error: TypeError: Cannot read property 'coordinates' of undefined
Nesting the requests in the success functions functions, but is there a better way to have the last request wait around for the other two?

What you're looking for is the ability to chain promises. "Once you receive the data from this one, call this one" is the general format. Angular takes advantage of the $q library. You can find documentation here.

Related

HTTP Post calls sequentially Angularjs

I am attempting to make sequential post requests with angular just changing the request body. The reason for this is I have a REST API that I am calling to create users but it takes some time to return. I wanted to basically send up the requests in batches essentially calling the same endpoint just different request body. I have seen other questions about calling functions sequentially but they always seem to be a set number of functions that do different things. I just can't wrap my brain around the obvious recursion here.
So far I have this function that returns the promise but I don't understand how to write the recursion to call this function to go through all of $scope.csvResults.
$scope.importUsersPromise = function(currentIndex, step) {
var nextInput = $scope.csvResults.slice(currentIndex, currentIndex+step);
var requestBodyUsers = {
"mode": "SCRIPT",
"inputParams": [
JSON.stringify(nextInput)
]
};
return $http({
method: 'POST',
url: api_url + "v1/serverAction/",
headers: {
"Authorization":"user",
"Content-Type":"application/json"
},
requestBodyUsers
});
};
Say you have an array users with all the different request bodies. Then you do something like this:
var users = [/* your request bodies */];
var errors = [/* will store the errors */];
// make the first api call
var promise = apiCall(users[0]);
// use .reduce to chain all requests
promise = users.slice(1).reduce(function(promise, user){
return promise.then(apiCall.bind(null, user));
}, promise);
promise
.then(function(){
// do something when all users are inserted
})
.finally(function(){
// do something when all requests are done
// even if some of them have failed
console.log(errors);
})
function apiCall(user) {
return $http({... })
}
You have to keep in mind that if one of the requests fails the chain is broken and the following requests will not be send. If you want to send them anyway you should use .finally and optionally .catch to collect the errors:
// use .reduce to chain all requests
promise = users.slice(1).reduce(function(promise, user){
return promise
.catch(err => errors.push(err)) // fail handler (optional)
.finally(apiCall.bind(null, user)); // always make the next api call
}, promise);
It is good idea for you to check angular's documentation if not already ;)
You can put all the request set in a requestArray.
Please check out this link.

Handle syntax error in JSON coming from API

Imagine the following syntax error in JSON (, instead of :):
[
{
"name": "anna",
"email": "anna#gmail.com",
"town", "london"
},
...
]
I am wondering if it is possible to handle this error instead of getting an exception, getting the erroneous object, correct the error and go on with the correct version.
Here is a part of my Angular service; I am trying to get text and not JSON data but it does not work...
angular.module('mine', [])
.config(function($sceProvider) {
// Completely disable SCE.
$sceProvider.enabled(false);
})
.config(['$sceDelegateProvider', function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist([
'self',
'http://www.mocky.io/v2/5807df4a10000004122b74e2'
]);
}])
.config(function ($httpProvider) {
$httpProvider.interceptors.push(function($q) {
return {
'request': function(config) {
config.headers.Accept = 'text/plain';
return config;
},
'response': function(response) {
try {
// try to parse it
response.data = JSON.parse(response.data);
} catch (ex) {
// try to fix it
console.log("error " + ex);
console.log(response.data);
response.data = {fixed_data : "data"};
}
// return the corect data.
// note that the original response.data WILL BE CHANGED and this is expected.
return response;
}
};
});
})
angular.module('mine').factory('MyFactory', ['$http','$q', function MyFactory($http,$q) {
return {
getData: function() {
var deferred = $q.defer(),
config = {
params: { }
},
url="http://www.mocky.io/v2/5807df4a10000004122b74e2";
$http.jsonp(url,config)
.then(
function (response) {
deferred.resolve(response.data);
},
function (error) {
console.log(error);
return $q.reject('Error retrieving data');
}
);
return deferred.promise;
}
};
}]);
Is there anyway of directing the above promise into the success callback, retrieving the erroneous JSON and correcting it? How may I code that according to the above example?
Or maybe something easier, how to retrieve text and not JSON data from $http.jsonp as not to be driven to the failure callback?
TL;DR
Edits after further understanding the OP problem:
In the generic case where you want to edit content of a response you can do it with "Interceptors" yet the response should be legitimate to begin with. That is, if you wanted to change numeric strings to integers in an otherwise correct JSON - it would be possible.
In the situation the OP is heaving where the JSON is malformed - it is just not possible!
The long story
First
Getting into a classic XY problem!
You should really ask yourself why is the JSON broken and not attempt to fix it in the client code.
Think of it - You will only get into more problems if you fix it now, and later someone will fix the API - then you will have the broken code.
What if the JSON should have been:
[
{
"name": "anna",
"email": "anna#gmail.com",
"addresses": [{"town": "london", ...}, ...]
},
...
]
Or (god forbid):
[
{
"name": "anna",
"email": ["anna#gmail.com","town", "london"]
},
...
]
You see - my point is - The API is broken, it can be anything. You should fix the API. And if this API is not yours to fix -> use some other API or contact the owner to fix it.
JSONP
JSONP is a way to let APIs to call directly into your code.
You must trust this API. If an API would have giving me malformed JSONs - I would stay away!
In short, the way JSONP works in Angular (or everywhere actually) is by injecting a <script> tag into the DOM with src pointing to the URL of the JSONp request.
The server will pad the JSON data with a function name (most often callback but it can be any globally accessible function (Angular is using angular.callbacks._xyz) and will send it.
The browser then invokes the script that was downloaded from the src.
Now, the thing is that it is the browser calling the script. It is not in the hands of Angular. And that is exactly the problem the OP is confronting - the script must be evaluated as a correct JavaScript to begin with and the browser is doing that, not Angular. It is a MUST. You cannot get in the middle of it. It could pose a security risk if you do. This is why, for instance, the response of a JSONP request will always (by convention...) be returned with MIME type of application/javascript no matter what you ask for.
Warring - here be dragons!
I urge you not to go in this path!
If you are insisting in getting from a JSONP call a JSON with errors (and by errors I mean that the JSON can be parsed as object yet there are some thing you want to change in that object) you could try to add "Interceptors"
.config(function ($httpProvider) {
$httpProvider.interceptors.push(function($q) {
return {
'request': function(config) {
// here you can edit the request.
return config;
},
'response': function(response) {
// response.data will hold your bad data
// you could edit it
response.data = fix(response.data);
// return the correct data.
return response;
}
};
});
})
Note that you could also Overriding the Default Transformations
Also, make sure to also to:
// Whitelist the JSONP endpoint that we are using to show that we trust it
.config(['$sceDelegateProvider', function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist([
'self',
'https://your.api.url/**'
]);
}])
And if all went well you will be able to call:
//uncomment {jsonpCallbackParam: 'callback'} if your jsonp callback
//parameter at the backend uses some other name but the default 'callback'
$http.jsonp(https://your.api.url/*,{jsonpCallbackParam: 'callback'}*/)
.then(function(response) {
$scope.status = response.status;
$scope.data = response.data;
}, function(response) {
$scope.data = response.data || 'Request failed';
$scope.status = response.status;
});
You can use douglascrockford's JSON-js.If it is not a valid json it will throw an error so that you can catch using try/catch and return a new Promise or simple true/false. If you don't use the library it will fallback to built in parser.
$http({
method: "GET",
url: '../data/data-feed.json'
})
.then(
function (response) {
console.log(response);
try {
JSON.parse(json);
console.log("valid");
} catch (e) {
console.log("invalid");
// correct the invalid json here
}
},
function (error) {
console.log('error');
}
);
Default JSON parser behavior
function parseJSON (jsonString){
try {
var jString = JSON.parse(jsonString);
if (jString && typeof jString === "object") {
return jString;
}
}
catch (e) { }
return false;
};
var inValidJson = '[{"name": "anna","email": "anna#gmail.com","town", "london"}]';
var validJson = '[{"name": "anna","email": "anna#gmail.com","town": "london"}]';
console.log("if invalid returns: ", parseJSON(inValidJson));
console.log("if valid get original object: ",parseJSON(validJson));
Short answer: no there isn't.
Long answer: If your JSON serialisation does not work in the backend, you have basically to parse a string an construct a new Object by yourself. There is no library which does that for you. And think of a different backend service.
I agree to #Florian's answer in general - there is no simple way of doing so.
I think that You should:
try to find a clever way to find the problem place;
replace the comma(s);
parse to JSON anew.
Idea of finding problem place: After every second (even) value there needs to be a comma. After every second (odd one - 1., 3., 5) - a colon. On each New { You have to start a new count. It probably is a pain in the ass, but doable.
It's possible, but cumbersome. For reasons It-Z went into, generally the best practice would be to fix the JSON being served by the API or to find a new API to work with. Assuming you have your reasons for not doing that, here is the logic flow:
catch the error from JSON.parse and feed it to a new function, along with the unparsed response string
in that new function
if you know you're only going to have this one case for your error, create logic to find and fix it; regex will be your friend here
if you're trying to catch multiple types of syntax problems, you'll need more complex regex and much more complex logic
pass the corrected string back to the original JSON.parse function

Keep reading from Angular JS HTTP call

I am working on a web system, but fairly new to Angular JS.
The feature I am working on currently needs to do a POST call using Angular. That's not really difficult to do in Angular. But I want to keep reading from the socket.
The call I am doing takes a very long time (as intended) but I want the status of the call to be visible in my Angular app. So I'd like to send data back with the status, and this to be live visible in my app.
What's the best approach for this? I found the WebSocket demonstrations for Angular, but in that case I'd have to create my own HTTP implementation on top of WebSockets to be able to send POST requests.....
HTTP requests using the $http service take place in the background, so you can show anything you want while the request is actually being made. To get a sense of the status of the request it depends on how you'd like to measure that status.
Progress events on the $http object aren't slated to be added to Angular until 1.6, so if the delay is related to a large file upload you might need to create a service that wraps a raw XMLHttpRequest object instead of using $http. Here's an example
from the MDN docs:
var oReq = new XMLHttpRequest();
oReq.addEventListener("progress", updateProgress);
oReq.addEventListener("load", transferComplete);
oReq.addEventListener("error", transferFailed);
oReq.addEventListener("abort", transferCanceled);
// progress on transfers from the server to the client (downloads)
function updateProgress (oEvent) {
if (oEvent.lengthComputable) {
var percentComplete = oEvent.loaded / oEvent.total;
// ...
} else {
// Unable to compute progress information since the total size is unknown
}
}
If the delay is serverside - waiting for a long running database query, for example, you might want to just fake a progress meter based on the average runtime of the query or show an indeterminate bar:
It sounds like you somehow have a way to monitor the progress on the serverside. In that case, you can make another request while the first one is in progress to get the progress information. You will probably need to send some state (like a query ID or request ID) to correlate the two requests.
XHRCallToTheRestService()
.then(function(result){
//Hide status bar, data is back
});
readMyStatus()
.then(function(status){
if(status == "finished"){
//Do nothing
} else{
readMyStatus();
}
});
You can use interceptor. Whenever you do http call, this will intercept the call before send and after return from the server.
$httpProvider.interceptors.push(['$q', '$location', 'localStorageService', function ($q, $location, localStorageService) {
return {
request: function (config) {
// You can do some stuff here
return config;
},
response: function (result) {
return result;
},
responseError: function (rejection) {
console.log('Failed with', rejection.status, 'status');
return $q.reject(rejection);
}
}
}]);
Or you can use https://github.com/McNull/angular-block-ui
you could create a custom service using $HTTP to do a POST request like this:
site.factory('customService', ['$http', function($http){
var httpProto = {
success : function(response, status, headers, config) {
return response;
},
error : function(error, status, headers, config) {
return error;
}
};
thePostRequest: function(dataRequest){
var output = $http.post(
'http://74.125.224.72/',
dataRequest
);
output.prototype = httpProto;
return output;
}
}
For web sockets a friend pointed me to socket.io and I have used them successfully for Angular in several SPAs. Check this material for more info.
G00d 1uck.

Angular json data showing in console, but not in the view

I am newbie at Angular and I don't really know how to solve this problem. I have looked online and read the documentation, but I haven't found a proper answer. I also asked coworkers about this issue. They couldn't figure it out to assist me, so I thought it would be best to ask you guys about what the best way to solve this is.
Basically, the app is supposed to change the json data when the user clicks link in the menu. It is supposed to grab the current index and then display the corresponding data based on that index in the array. I will post the code that I have so far.
Here is a link to the code on Plunker.
app.factory('quest', ['$http', function($http) {
return $http({
method: 'GET',
url: 'data/study.json'
}).success(function(data) {
return data;
})
.error(function(err) {
return err;
});
}]);
In order to use HTTP requests I would suggest to use the following pattern:
app.factory('quest', function($http, $q) {
var promise;
return {
getQuests: function() {
// $http returns a promise, so we don't need to create one with $q
promise = $http.get('data/study.json')
.then(function(data) {
return data;
}, function(err) {
return $q.reject(err);
});
return promise;
}
}
});
So later you can fetch the factory in your Controller with:
quest.getQuests()
.then(function(data) {
$scope.data = data;
}, function(res) {
if(res.status === 500) {
// server error, alert user somehow
} else {
// probably deal with these errors differently
}
});
You can find the pattern here: https://stackoverflow.com/a/18383845/1918775
There is also a good example of saving data under a factory so you need only one HTTP request to get data from web-service.
Thanks everyone for your answers. I ended up figuring it out. What I did was created a function to search the array and I passed in the current term. The function returned a data associated with that item.

Angular Facebook service hits API call limit

I am trying to call my Facebook service within a ng-repeat but somehow the Facebook API call limit is hit very quickly.
I have a service as so:
angular.module('core').factory('Facebook', ['$q',
function ($q) {
return {
getMutualFriends: function (fbUserId) {
var deferred = $q.defer();
var path = "/" + fbUserId;
FB.api(
path,
{
'fields': 'context.fields(mutual_friends)'
},
function (response) {
if (!response || response.error) {
deferred.reject('Error occured');
} else {
deferred.resolve(response);
}
}
);
return deferred.promise;
}
};
}
]);
And within my controller, I have a function that calls the service:
$scope.getMutualFriendsCount = function (fbUserId) {
if (fbUserId === '' || fbUserId === undefined) return 0;
Facebook.getMutualFriends(fbUserId)
.then(function (response) {
// untested response
return response.context['mutual_friends']['summary']['total_count'];
});
}
In my template, I have data-ng-repeat="profile in profiles" and for each profile, I try to bind the results data-ng-bind=getMutualFriends(profile.fbId).
The service manages to communicate with the FB servers until I start noticing that there are way too many calls during the loop and the call limit is hit very quickly (within 1 or 2 refreshes on my dev machine on page of 20 profiles only). Does anyone have any idea on how I could better design the approach to obtain mutual friends for multiple ids?
You shouldn't call a function that makes an HTTP request from a watched expression, whether it's ng-bind or the equivalent {{ }}. This expression, and the subsequent HTTP call` will get called on every digest cycle - clearly not what you expect or need.
Instead, get the data that you need and store it, for example, with each profile, and access that value from within the ng-repeat.
Also, as per the comment above, you should consider calling Facebook in a batch. Create a separate service to handle all of this complexity and expose a simple API to the controller.

Categories

Resources