I'm sending a model to server via $http.post, but, say, empty dates must be deleted, ids must be converted to int, in float values comma must be replaced with dot. These are restrictions of the server-side json api, so I'm looking for a way to modify $http request. Complicated part is that the modification rules depend on a api method name, which itself is a part of request.
The most straightforward way is to declare a modifying function and pass model to that function right before $http.post
$scope.method1Adapter = function(model) {
var data = angular.copy(model);
// 30 lines of modification code
return data;
};
$http.post("/api", {method: "method1", "data": $scope.method1Adapter($scope.data)})
but I think it's a little bit spaghettysh.
Better way is a factory that gets a method name and returns an adapter callback.
coreApp.factory("httpAdapter", function() {
return {
get: function (method) {
if (method == 'method1') {
return function (model) {
var data = angular.copy(model);
// modifications
return data;
}
} else {
return function (model) {
return model;
}
}
}
}
});
so i can add this to $httpProvider.defaults.transformRequest callbacks chain
coreApp.config(function($httpProvider, httpAdapterProvider) {
$httpProvider.defaults.transformRequest.unshift(function(post) {
if (post && post.data && post.data) {
post.data = httpAdapterProvider.$get().get(post.method)(post.method);
}
})
});
And still I don't like that, because api for my application has 16 methods, and it would require 5 adapters which is about 100 lines of code hard to maintain.
Any ideas about more clean and neat solution? Thank you.
I wouldn't chain adapters here because, as you said, it would be hard to maintain.
My advice would be to use the $http interceptors (not the responseInterceptors, which are deprecated, but the normal one, see http://docs.angularjs.org/api/ng.$http).
Notice in that you have access to the "config" object that has the request url, amongst other interesting properties.
It won't be superneat but at least the problem can be contained in one isolated part of your codebase.
Related
Angularjs app here.
There are 2 controllers that do similar things.
In particular, they have an interval. Each 10 seconds they go to their own service.
These 2 different services also do similar things. Most important is that they go to an URL that looks like this:
example.com/fromTimestamp=2019-11-21T15:13:51.618Z
As the two controllers start more or less at the same time, in the example above they could generate something like:
controller/service 1: example.com/fromTimestamp=2019-11-21T15:13:51.618Z
controller/service 2: example.com/fromTimestamp=2019-11-21T15:13:52.898Z
This is because the parameter is created in the service with his line:
var timestamp = fromTimestamp ? '&fromTimestamp=' +fromTimestamp.toISOString() : '';
So maybe there will be a difference of some seconds. Or even only a difference of milliseconds.
I would like to make only one request, and share the data fetched from http between the two services.
The most natural approach would seem to be using cache.
What I could understand is that this call could make the trick:
return $http.get('example.com/fromTimestamp=2019-11-21T15:13:51.618Z', {cache: true});
But looking in the dev tools it is still making 2 requests to the server. I guess this is because they have 2 different urls?
If that is the problem, what could be another approach to this problem?
In my apps, when face with this problem, I use the $q provider and a promise object to suspend all calls to the same endpoint while a singleton promise is unresolved.
So, if the app makes two calls very close together, the second call will not be attempted until the promise created by the first call is resolved. Further, I check the parameters of the calls, and if they are the same, then the original promise object is returned for both requests. In your case, your parameters are always different because of the time stamp. In that case, you could compare the difference in time between the two calls, and if it is under a certain threshold in miliseconds, you can just return that first promise object. Something like this:
var promiseKeeper; //singleton variable in service
function(endpointName, dataAsJson) {
if (
angular.isDefined(promiseKeeper[endpointName].promise) &&
/*promiseKeeper[endpointName].dataAsJson == dataAsJson && */
lastRequestTime - currentRequestTime < 500
) {
return promiseKeeper[endpointName].promise;
} else {
deferred = $q.defer();
postRequest = $http.post(apiUrl, payload);
postRequest
.then(function(response) {
promiseKeeper[endpointName] = {};
if (params.special) {
deferred.resolve(response);
} else {
deferred.resolve(response.data.result);
}
})
.catch(function(errorResponse) {
promiseKeeper[endpointName] = {};
console.error("Error making API request");
deferred.reject(extractError(errorResponse));
});
promiseKeeper[endpointName].promise = deferred.promise;
promiseKeeper[endpointName].dataAsJson = dataAsJson;
return deferred.promise;
}
}
Hi I'm trying to update my database with function that returns a number
$scope.sum = function()
{
return $scope.inp + $scope.points;
};
this function will update the record in object points, column name and id 1:
$scope.addPoint = function() {
PointService.addPoint($scope.sum, 1)
.then(function(result) {
$scope.inp = 0;
getMyPoints();
});
}
addPoint = function(id,points)
{
return $http.put(getUrlForId(1),points,name);
}
the error is: Error details: Cannot convert type 'int' to 'System.Collections.Generic.Dictionary'
the data type of the field is Float.
Any idea what is wrong with the code?
you are passing function reference to PointService.addPointer(),
use this:
$scope.addPoint = function() {
PointService.addPoint($scope.sum(), 1) // NOT PointService.addPoint($scope.sum, 1)
.then(function(result) {
$scope.inp = 0;
getMyPoints();
});
}
this will execute your function and pass the output (id parameter) to addPoint function, further, for more safe side, you can return Number from $scope.sum() i.e.
$scope.sum = function()
{
return Number($scope.inp + $scope.points);
};
This looks like an issue with how you're contacting Backand. You use the following code to send your points over:
addPoint = function(id,points)
{
return $http.put(getUrlForId(1),points,name);
}
This is an older version of calling the Backand API that is manually constructing a PUT request, and putting "points" and "name" as the "Data" and "config" parameters to $http. With an object update via PUT, you'll need to provide the updates as an object. So if you wanted to update the points and the name of the object (and I'm doing some assuming based upon what I can tell from the code snippet above), you'd need to encapsulate these properties in an object that has the following general format:
{
"field_name_1":"new value_1",
"field_name_2":"new value_2",
etc ...
}
This should then be sent as the body of the request. So, for your code, change it to the following and see if this helps:
addPoint = function(id,points)
{
return $http.put(getUrlForId(1),{points: points, name: name});
}
To give more info on why you're seeing this particular error, Backand is depending on this JSON format in the body. Our platform should definitely do more validation (and I'll create a ticket for the devs to handle non-conforming input more gracefully), but at the moment we simply take the body of the request, convert it to a dictionary object, then begin the requested operation. As your code above sends only "1.0" as the body, this fails the conversion into a dictionary, causing the stack exception and the issue you are seeing.
As a note, we offer a new SDK that encapsulates these HTTP methods, performing the authentication header generation and HTTP messaging for you, providing promises to handle responses. You can find it on our Github page at https://github.com/backand/vanilla-sdk. To make the same call using the new SDK, the code would resemble the following:
backand.object.update("your object name", 1, {name: name, points: points})
.then(function(response){
console.log(response.data);
});
I have been reading about $q and promises for days now and I seem to understand it...somewhat. I have the following situation in practice:
An $http request is made and checks whether a subsequent call can be made.
If the first call fails, return "no data", if it succeeds and says a call can be made, the second call is made, if not - "no data" again. If the second call succeeds, it returns data, if not - "no data". It looks like this (approximately, I simplified for general idea, so don't worry about the minor mistakes here):
return $http.get (something)
.then(function(allowedAccess){
if(allowedAccess){
return $http.get (somethingElse)
.then( function(result){return {data:result} },
function(error){return {data:"n0pe"} }
)
} else {
return {data:"n0pe"}
}
},
function(){ return {data:"n0pe"} });
I was told to use $q here. I don't really understand how or why I would. The $http calls are promises already.
If there is a way to make this cleaner, I don't see it. Just got done re-reading this post on the subject. Essentially, am I missing something / is there a better way to do this?
Edit: Also just re-read a tutorial on chaining promises - it doesn't handle call failures at all. Basically posting this as due diligence.
Edit 2: This is more of an elaborate on the theory I am asking about, excerpt from the first article:
This is a simple example though. It becomes really powerful if your then() callback returns another promise. In that case, the next then() will only be executed once that promise resolves. This pattern can be used for serial HTTP requests, for example (where a request depends on the result of a previous one):
This seems to be talking about chains like this:
asyncFn1(1)
.then(function(data){return asyncFn2(data)})
.then(function(data){return asyncFn3(data)})
So, if I understand correctly this a). Doesn't apply to me because I don't have a 3rd function. b). Would apply to me if I had three functions because while I run an if statement inside the first $http request, and only inside the if statement do I return another promise. So, theoretically, if I had three async functions to chain, I would need to put my if statement inside a promise?
Promises really help with code composition of making async calls. In other words, they allow you to compose your code in a similar manner to how you would compose a synchronous set of calls (with the use of chained .thens) and as if it the sync code was in a try/catch block (with .catch).
So, imagine that your HTTP calls were blocking - the logic you have would look like so:
var allowedAccess, data;
try {
allowedAccess = $http.get(something);
if (allowedAccess){
try{
var result = $http.get(somethingElse);
data = {data: result};
} catch (){
data = {data: "n0pe"};
}
} else {
data = {data: "n0pe"};
}
} catch (){
data = {data: "n0pe"};
}
return data;
You could simplify it a bit:
var allowedAccess, result;
try {
allowedAccess = $http.get(something);
var result;
if (allowedAccess) {
result = $http.get(somethingElse);
} else {
throw;
}
data = {data: result};
} catch () {
data = {data: "n0pe"};
}
return data;
And that would translate to the async version of:
return $http
.get(something)
.then(function(allowedAccess){
if (allowedAccess){
return $http.get(somethingElse);
} else {
return $q.reject(); // this is the "throw;" from above
}
})
.then(function(result){
return {data: result};
})
.catch(function(){
return {data: "n0pe"};
})
At least, this is the reasoning you could apply when composing code with branches and async calls.
I'm not saying that the version I presented is optimal or shorter - it is, however, more DRY because of a single error handling. But just realize that when you do .then(success, error) it is equivalent to try/catch over the previous async operation - this may or may not be needed depending on your specific circumstance.
This is how I would code this sort of problem:
// returns a promise that resolves some endpoint if allowed
function getDataWithAccess(allowed){
return allowed ? $http.get(someEndpoint) : $q.reject();
}
// do something with data
function handleData(data){
// do stuff with your data
}
// main chain
$http.get(accessCredEndpoint)
.then(getDataWithAccess)
.then(handleData)
.catch(function(err){
return { data: "n0pe" };
});
Yes, this is very much like New Dev's answer, however I wanted to make a point of extracting the functions into their own blocks. This makes the overall code much more readable.
$q will help reduce pyramid of calls like this:
async-call1.then(...
aysnc-call2.then(...
This blog post - http://chariotsolutions.com/blog/post/angularjs-corner-using-promises-q-handle-asynchronous-calls/ - offers a clean way of making multiple HTTP requests. Notice the cleaner approach using $q. In case you were hitting a single HTTP endpoint, using your method would have been just fine. I'd say, what you have is fine also; $q might allow greater flexibility in the future.
The blog post describes a service while using $q and the code looks cleaner.
service('asyncService', function($http, $q) {
return {
loadDataFromUrls: function(urls) {
var deferred = $q.defer();
var urlCalls = [];
angular.forEach(urls, function(url) {
urlCalls.push($http.get(url.url));
});
// they may, in fact, all be done, but this
// executes the callbacks in then, once they are
// completely finished.
$q.all(urlCalls)
.then(...
I am a beginner with promises also, so take this with a grain of salt.
I'm trying to rewrite the code for http://m.amsterdamfoodie.nl in a more modern style. Basically single page Angular app downloads a set of restaurants with locations and places them on a map. If the user is the Amsterdam area then the user's location is added too, as are the distances to places.
At present I manage the asynchronous returns using a lot of if (relevant object from other async call exists) then do next step. I'd like to make more use of promises would be better.
So, flow control should be:
Start ajax data download, and geolocation call
if geolocation returns first, store coords for later
once ajax data is downloaded
if geolocation available
calculate distances to each restaurant, and pass control to rendering code
else pass control immediately to render code
if geolocation resolves later, calculate distances and re-render
The patterns I find on the internet assume that all async calls must return successfully before continuing, whereas my geolocation call can fail (or return a location far from amsterdam) and that's OK. Is there a trick I could use in this scenario or are the conditional statements really the way to go?
Every time you use .then, you essentially create a new promise based on the previous promise and its state. You can use that to your advantage (and you should).
You can do something along the lines of:
function getGeolocation() {
return $http.get('/someurl').then(
function resolveHandler(response) {
// $http.X resolves with a HTTP response object.
// The JSON data is on its `data` attribute
var data = response.data;
// Check if the data is valid (with some other function).
// By this, I mean e.g. checking if it is "far from amsterdam",
// as you have described that as a possible error case
if(isValid(data)) {
return data;
}
else {
return null;
}
},
function rejectHandler() {
// Handle the retrieval failure by explicitly returning a value
// from the rejection handler. Null is arbitrarily chosen here because it
// is a falsy value. See the last code snippet for the use of this
return null;
}
);
}
function getData() {
return $http.get('/dataurl').then(...);
}
and then use $q.all on both promises, which in turn creates a new promise that resolves as soon as all the given promises have resolved.
Note: In Kris Kowal's Q, which Angular's $q service is based on, you could use the allSettled method, which does almost the same as all, but resolves when all promises are settled (fulfilled or rejected), and not only if all promises are fulfilled. Angular's $q does not provide this method, so you can instead work your way around this by explicitly making the failed http request resolve anyways.
So, then you can do something like:
$q.all([getData(), getGeolocation()])
.then(function(data, geolocation) {
// `data` is the value that getData() resolved with,
// `geolocation` is the value that getGeolocation() resolved with.
// Check the documentation on `$q.all` for this.
if(geolocation) {
// Yay, the geolocation data is available and valid, do something
}
// Handle the rest of the data
});
Maybe I'm missing something... but since you have no dependencies between the two async calls, I don't see why you can't just follow the logic you outlined:
var geoCoordinates = null;
var restaurants = null;
var distances = null;
getRestaurantData()
.then(function(data){
restaurants = data;
if (geoCoordinates) {
distances = calculate(restaurants, geoCoordinates);
}
// set $scope variables as needed
});
getGeoLocation()
.then(function(data){
geoCoordinates = data;
if (restaurants){
distances = calculate(restaurants, geoCoordinates)
}
// set $scope variables as needed
});
So when I save a model on the backend, My api send back a response telling everything went fine and giving you some other pointers in json format
My problem is that backbone think I want to use that response as attributes of my model and automatically dump them in the model attributes..
I just saved it on the front-end and do not want to save the attributs again.
That is the what Backbone.Model.parse is for. By default, it is just a pass-through, so you don't need to call "super".
Let's say you only care about two properties that come back (id and foo) and you don't care about anything else:
var myModel = Backbone.Model.extend({
parse : function(resp, xhr) {
return {
id: resp.id,
foo: resp.foo
};
}
});
Note that I included "id" in my example. It is really important that creates (POST) return an id property. Without it, the Backbone model won't know how to update/delete in the future. Even if it has a different name for id (like "objectId"), you should still set the id in this function.
Indeed that's the default behaviour, and if you want to change it, you have to overwrite some Backbone functions.
Looking at how save is implemented, you have two options - either overwrite save for your model, or overwrite parse to make it aware of the data you are sending.
http://documentcloud.github.com/backbone/docs/backbone.html#section-41
Or, you could give up sending the 'pointers' in the response, because an empty response means that the model does not change after save.
I have the exact issue you are encountering. Backbone is a pretty young framework with the additional fact that javascript is really dynamic. So the saying that there are a thousand ways to solve a problem applies really well here.
I think a more suitable way to go about this is to employ something called Mixins. Here's what I did:
define([
'underscore',
'backbone',
'jquery'
], function (_, Backbone, $) {
return {
parse: function(response, xhr){
var data = response;
if(response.response && response.response.status != 0){
return {};
}
if(response.response && response.response.data)
{
data = _.first(response.response.data);
if(typeof data == 'undefined'){
data={};
}
}
if(_.isFunction(this.postParse)){
return this.postParse.call(this, data);
}
return data;
}
}
});
As you can see, I've overridden the Backbone.Model.parse prototype method and came up with my own that takes in a response. And parse the response according to my server's spec. In you case, you would implement whatever it takes to understand your server's response.
With the ground work out of the way, specifying a model is very easy:
var group = Backbone.Model.extend(
_.extend({}, ModelMixin, {
initialize:function () {
}
})
);
return group;
Voila! All the parse methods and checks that you need to write is encapsulated in that one ModelMixin "superclass".