angularjs resource limit changes after first call - angularjs

Problem description
Im using the angular resource to get data from my server. I've extended it a bit to make sure all of my resources have security headers.
Problem is that on the second get request and on, my get requests are sent with limit=0, and only the first get request is sent correctly (with limit=12).
Code part
This is my base resource factory (for making sure all resource contain the keys and everything):
app.factory('SecuredFactory', function($resource){
var DEFAULT_ACTIONS = {
'get': {method:'GET'},
'query': {method:'GET', isArray:true},
};
var DEFAULT_PARAMS = {
'limit': 12,
'format': 'json'
};
for(var key in DEFAULT_ACTIONS){
DEFAULT_ACTIONS[key]['headers'] = <headers object>;
}
var securedResource = function(url, paramDefaults, actions){
for (var attrname in actions) {
DEFAULT_ACTIONS[attrname] = actions[attrname];
}
for (var attrname in paramDefaults) {
DEFAULT_PARAMS[attrname] = paramDefaults[attrname];
}
var defaultResource = $resource(url, DEFAULT_PARAMS, DEFAULT_ACTIONS);
return defaultResource;
};
return securedResource;
});
And this is an example of how I creat a specific factory out of the secured one:
app.factory('QuestionFactory', function(SecuredFactory, Constants){
var url = Constants.SERVER_URL + 'question/';
var Task = SecuredFactory(url);
return Task;
});
And this is finally how I use it, for example:
// filtering example (not important for this matter):
var filtering = {author: "Daniel"};
var contents = [];
var resource = QuestionFactory;
resource.get(filtering, function (res) {
// success fetching
$scope.contents = $scope.contents.concat(res['objects']);
}
// failed fetching
, function (err) {
}
);
The requests
first request:
question?format=json&limit=12&offset=0
second request and on:
question?format=json&limit=0&offset=0

My problem was that the DEFAULT_PARAMS variable was declared as global. I didn't realize that invoking the secured factory with {limit: 0} will override the global, therefore changing the limit to 0 for ALL of my resources.
Changing the securedFactory to a service and moving the "globals" into the returned function solved it. Had to add new ofcourse before every securedService call.

Related

AngularJS get value from API only if not already set

I have service to get some data from API and serve them to application.
Simple function like this:
getEnvironmentStatus() {
var _this = this;
var req = {
method: "GET",
url: "/api/system/hosting",
headers: {},
data: {}
}
return _this.$http(req);
}
In some other place I have:
determineHostingEnv() {
var _this = this;
this.$env.getEnvironmentStatus()
.then(function(response){
_this.EnvHositng = response.data.cloud_hosted;
}, function(error) {
});
}
If I need the same information in other place (other controller), I would need to call api again.
How can I make getEnvironmentStatus() function to call API only once and store data in local variable, so it can serve that variable next time it is asked for it, instead of calling API?
Also, what if that value will get requested a few times before the first API will return value? Can I prevent calling that API a few times?
One can cache the promise:
httpPromiseCache = null;
getEnvironmentStatus() {
var _this = this;
var req = {
method: "GET",
url: "/api/system/hosting",
headers: {},
data: {}
}
if (!_this.httpPromiseCache) _this.httpPromiseCache = _this.$http(req);
return _this.httpPromiseCache;
}
The service will only execute the HTTP request once.

AngularJS : $resource, response Object or Array [duplicate]

There are two ways I can GET a REST resource by ID:
GET /users/1
GET /users/1,2
The first returns a single object like {id:1,name:"John"} while the second returns an array like [{id:1,name:"John"},{id:2,name:"Jill"}].
Please, no arguments about whether or not this is legit REST; just accept that a service has this and I need to work with it.
angular's $resource actually intelligently handles it from the request side:
User.get({users:['1','2']})
transforms into
GET /users/1,2
But it doesn't handle the response well. It expects a single object. If I change the definition to isArray:true, then it fails on a single request GET /users/1
How do I get it to intelligently handle both?
EDIT: I did some weird hacking to get it to work, but would prefer a native method:
factory('Resource',['$resource','_',function ($resource,_) {
return function(url,params,methods){
var defaults = {
getSingle: {method: 'get', isArray:false},
getMultiple: {method: 'get', isArray:true}
}, resource = $resource(url,params,angular.extend(defaults,methods)), urlParams = {};
_.each(url.split(/\W/), function(param){
if (param && (new RegExp("(^|[^\\\\]):" + param + "\\W").test(url))) {
urlParams[param] = true;
}
});
// do not override if they did
if (!(methods||{}).get) {
resource.get = function (params,success,error) {
// look for multiples
var isMultiple = false;
_.each(params,function (val,key) {
if (key && urlParams[key] && angular.isArray(val)) {
isMultiple = true;
return(false);
}
});
return this[isMultiple?"getMultiple":"getSingle"](params,success,error);
};
}
return(resource);
};
}]).
The normal convention is to create a Resource.get() method for single objects, and a Resource.query() method for arrays of them.
I had a similar issue with the WP-API when trying to do a PUT request to a post. Currently that API returns an object representing the post if everything went okay, but if there is an error (eg the authorization credentials don't match) then it returns an array of errors. So I was getting the error Error: [$resource:badcfg] Error in resource configuration. Expected response to contain an object but got an array.
I managed to find a solution by using the transformResponse property on my custom action object. I define a function which inspects the response and then if it is an array, it converts the array into an object. This seems to work okay, and seems a bit less complex than the solution you posted in your update:
var wpPost = $resource(PATH_TO_WORDPRESS_API + 'posts/:id', {},
{
'update': {
method: 'PUT',
params: {id: '#id'},
transformResponse: function(data) {
var response = angular.fromJson(data);
if (response.length) {
// the response is an array, so convert it into an object
var object = {};
for( var i = 0; i < response.length; i ++) {
object[i] = response[i];
}
return object;
} else {
return response;
}
}
}
});

$resource .then() throwing error after receiving the resource

I'm trying to add a layer of abstraction between the angular $resource received and my controller. I have to perform a few operations like filtering, so I setup a service to perform these in-between functions.
I was able to set up this first resource call with no problems:
/**
* Get Grid
*
* retrieves grid resource and serializes the grid, rows, columns and widgets themselves
*/
this.getGrid = function() {
var gridResource = new GridResource();
var response = gridResource.$query();
var accGrid = new Grid;
response.then(function(response) {
angular.forEach(response.grid, function(row) {
var accRow = new Row;
angular.forEach(row.columns, function(column) {
//Setting up new Column
var accColumn = new Column();
accColumn.setId(column.id);
//Setting up new Widget(s)
var accWidget = new Widget;
accWidget.setId(column.widget.id);
accWidget.setName(column.widget.name);
accWidget.setType(column.widget.type);
accWidget.setChildren(column.widget.children);
accWidget.setVars(column.widget.vars);
accColumn.addWidget(accWidget);
accRow.addColumn(accColumn);
});
accGrid.addRow(accRow);
});
});
return accGrid;
};
This returns the Grid object with all of the populated parts.
However when I try to do perform the same method on a different endpoint, Angular complains:
http://puu.sh/eUSbx/3c15a8b13a.png
I only got to this point in the method:
/**
* Get all Widgets
*
* Retrieves all widgets belonging to the route, regardless if they are in the canvas or not
*/
this.getWidgets = function() {
var widgets = new Array();
var widgetResource = new WidgetResource();
var response = widgetResource.$query();
response.then(function(response) {
console.log(response);
});
return widgets;
};
If you're wondering about the $resource itself:
designService.factory('GridResource', ['$resource',
function($resource){
return $resource('view/canvas', {},
{
query: { method:'GET' },
save: { method:'POST' }
});
}]);
designService.factory('WidgetResource', ['$resource',
function($resource) {
return $resource('view/widget', {},
{
query: { method:'GET', isArray: true }
});
}]);
I'm a PHP guy moving into the wonderful weird world of frontend JS and could really use a pointer :sweaty-smile: thanks!
** Update ** I've learned how Angular uses then to catch error responses too, so I updated my query:
widgetResource.$query().then(
function(response) {
console.log(response);
},
function(error) {
console.log(error);
}
);
Which produced this error:
http://puu.sh/eUYYx/998c73600a.png
Your code seems good, Look around your dependency injection in your controller. You may have missed one or misspelled ?
** Got the answer **
Angular is so damn picky! The problem was I was trying to create a new resource object from one that was already given. Directly assigning the response to to the result of WidgetResource.query() was sufficient. This maybe due to the fact I have the WidgetResource.query() have the isArray property to true.
/**
* Get all Widgets
*
* Retrieves all widgets belonging to the route, regardless if they are in the canvas or not
*/
this.getWidgets = function() {
var widgets = new Array();
//var widgetResource = new WidgetResource();
var response = WidgetResource.query();
response.$promise.then(
function(response) {
console.log(response);
},
function(error) {
console.log(error);
}
);

Cancelling a request with a $http interceptor?

I'm trying to figure out if it is possible to use a $http interceptor to cancel a request before it even happens.
There is a button that triggers a request but if the user double-clicks it I do not want the same request to get triggered twice.
Now, I realize that there's several ways to solve this, and we do already have a working solution where we wrap $http in a service that keeps track of requests that are currently pending and simply ignores new requests with the same method, url and data.
Basically this is the behaviour I am trying to do with an interceptor:
factory('httpService', ['$http', function($http) {
var pendingCalls = {};
var createKey = function(url, data, method) {
return method + url + JSON.stringify(data);
};
var send = function(url, data, method) {
var key = createKey(url, data, method);
if (pendingCalls[key]) {
return pendingCalls[key];
}
var promise = $http({
method: method,
url: url,
data: data
});
pendingCalls[key] = promise;
promise.finally(function() {
delete pendingCalls[key];
});
return promise;
};
return {
post: function(url, data) {
return send(url, data, 'POST');
}
}
}])
When I look at the API for $http interceptors it does not seem to be a way to achieve this. I have access to the config object but that's about it.
Am I attempting to step outside the boundaries of what interceptors can be used for here or is there a way to do it?
according to $http documentation, you can return your own config from request interceptor.
try something like this:
config(function($httpProvider) {
var cache = {};
$httpProvider.interceptors.push(function() {
return {
response : function(config) {
var key = createKey(config);
var cached = cache[key];
return cached ? cached : cached[key];
}
}
});
}
Very old question, but I'll give a shot to handle this situation.
If I understood correctly, you are trying to:
1 - Start a request and register something to refer back to it;
2 - If another request takes place, to the same endpoint, you want to retrieve that first reference and drop the request in it.
This might be handled by a request timeout in the $http config object. On the interceptor, you can verify it there's one registered on the current request, if not, you can setup one, keep a reference to it and handle if afterwards:
function DropoutInterceptor($injector) {
var $q = $q || $injector.get('$q');
var dropouts = {};
return {
'request': function(config) {
// I'm using the request's URL here to make
// this reference, but this can be bad for
// some situations.
if (dropouts.hasOwnProperty(config.url)) {
// Drop the request
dropouts[config.url].resolve();
}
dropouts[config.url] = $q.defer();
// If the request already have one timeout
// defined, keep it, othwerwise, set up ours.
config.timeout = config.timeout || dropouts[config.url];
return config;
},
'requestError': function(reason) {
delete dropouts[reason.config.url];
return $q.reject(reason);
},
'response': function(response) {
delete dropouts[response.config.url];
return response;
},
'responseError': function(reason) {
delete dropouts[reason.config.url];
return $q.reject(reason);
}
};
}

Can Angular $resource.get handle both array and non-array for GET?

There are two ways I can GET a REST resource by ID:
GET /users/1
GET /users/1,2
The first returns a single object like {id:1,name:"John"} while the second returns an array like [{id:1,name:"John"},{id:2,name:"Jill"}].
Please, no arguments about whether or not this is legit REST; just accept that a service has this and I need to work with it.
angular's $resource actually intelligently handles it from the request side:
User.get({users:['1','2']})
transforms into
GET /users/1,2
But it doesn't handle the response well. It expects a single object. If I change the definition to isArray:true, then it fails on a single request GET /users/1
How do I get it to intelligently handle both?
EDIT: I did some weird hacking to get it to work, but would prefer a native method:
factory('Resource',['$resource','_',function ($resource,_) {
return function(url,params,methods){
var defaults = {
getSingle: {method: 'get', isArray:false},
getMultiple: {method: 'get', isArray:true}
}, resource = $resource(url,params,angular.extend(defaults,methods)), urlParams = {};
_.each(url.split(/\W/), function(param){
if (param && (new RegExp("(^|[^\\\\]):" + param + "\\W").test(url))) {
urlParams[param] = true;
}
});
// do not override if they did
if (!(methods||{}).get) {
resource.get = function (params,success,error) {
// look for multiples
var isMultiple = false;
_.each(params,function (val,key) {
if (key && urlParams[key] && angular.isArray(val)) {
isMultiple = true;
return(false);
}
});
return this[isMultiple?"getMultiple":"getSingle"](params,success,error);
};
}
return(resource);
};
}]).
The normal convention is to create a Resource.get() method for single objects, and a Resource.query() method for arrays of them.
I had a similar issue with the WP-API when trying to do a PUT request to a post. Currently that API returns an object representing the post if everything went okay, but if there is an error (eg the authorization credentials don't match) then it returns an array of errors. So I was getting the error Error: [$resource:badcfg] Error in resource configuration. Expected response to contain an object but got an array.
I managed to find a solution by using the transformResponse property on my custom action object. I define a function which inspects the response and then if it is an array, it converts the array into an object. This seems to work okay, and seems a bit less complex than the solution you posted in your update:
var wpPost = $resource(PATH_TO_WORDPRESS_API + 'posts/:id', {},
{
'update': {
method: 'PUT',
params: {id: '#id'},
transformResponse: function(data) {
var response = angular.fromJson(data);
if (response.length) {
// the response is an array, so convert it into an object
var object = {};
for( var i = 0; i < response.length; i ++) {
object[i] = response[i];
}
return object;
} else {
return response;
}
}
}
});

Resources