How to add dynamic, asynchronously loaded, default parameters to a resource - angularjs

I've got several resource classes that look like this:
.factory('SettingsResource', function (DefaultResource) {
var endpoint = '/settings';
var params = {};
var options = {};
return new DefaultResource(endpoint, params, options);
}
All the resource classes use DefaultResource as a "base":
.factory('DefaultResource', function ($resource, UserResource) {
var baseURL = 'rest',
baseParams = {
currentRole: function () {
return "Admin";
}
},
baseOptions = {
}
return function (endpoint, params, options) {
...
// Code ommited
};
}
As you can see there's a default parameter called 'currentRole' which I need to append to every request.
Currently the parameter is hard coded, but I should actually get it from an http call, e.g. like this:
currentRole: function () {
$resource.get('rest/currentRole').$then(function (response) {
return response.data;
});
}
I already tried using a Provider and trying to make the http call within the config function. But obviously one is not allowed to do this (only providers can be injected, I cannot use $resource.get within the config-function).
Has anyone a clue how I could do this? Basically: how can I add dynamic, asynchronously loaded default parameters to a resource?
I'd really appreciate your help!
Michael

Related

Javascript array : AJAX, JSON

I'm a newbee to JS , JSON and AJAX. I'm using it to develop a simple apps in my SAP env. I'm bit struck in converting the AJAX response to java array. What is have in the code is:
function addTable()
{
var urls = new Array();
$(document).ready(function ()
{
var params = getURLParam().split('?');
$.post("GetBayDetails.htm", {url: getURLParam(), params: params[1]})
.done(function (data)
{
var url = $.parseJSON(data);
urls.push(JSON.parse(url));
$.each(url, function (i, v)
{
push.urls[i] = v.bay;
});
});
});
alert(urls[2]);
}
but if I loop through "URLS" I do not see any value appended to the array. Please can anyone provide some help to get this fixed?
Try this. My changes are:
Use the "json" dataType argument to $.post so it parses the response automatically.
There's no need to use $(document).ready() inside a function that you call on demand. This is only needed when performing initial actions that have to wait for the DOM to be loaded.
It's not necessary to call JSON.parse(url), as this is already parsed.
The correct way to add to the urls array is urls.push(v.bay).
The preferred way to initialize an array is with [], not new Array().
alert(urls[2]) needs to be in the .done() function. Otherwise you're alerting before the AJAX call has completed.
function addTable() {
var urls = [];
var params = getURLParam().split('?');
$.post("GetBayDetails.htm", {url: getURLParam(), params: params[1]}, "json")
.done(function (url) {
$.each(url, function (i, v) {
urls.push(v.bay);
});
alert(urls[2]);
});
}
DEMO

$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);
}
);

angularjs resource limit changes after first call

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.

Is there a way I can pass in arguments to $http as part of an object?

I am using the following call:
$scope.retrieve = function () {
$resource('/api/Test/Retrieve')
.query({
subjectId: $scope.config.subjectId,
examId: $scope.config.examId,
userId: $scope.config.createdById
},
function (result) {
$scope.grid.data = angular.copy(result);
},
function () {
$scope.grid.data = null;
});
};
Is there a way that I could pass in the arguments through an object like this and use an $http call instead of a $resource. Also how could I move the success and error code blocks to there own functions?
Code below should work for posting with data.
$http.post("/api/Test/Retrieve", {
subjectId:$scope.config.subjectId,
examId:$scope.config.examId,
userId:$scope.config.createdById
}).success(
function(res){
//Some success handler here
}).error(
function(res){
//Some error handler here
});
There are lots of details you may want to include, if you need a GET with parameters check out the config parameter and it's properties:
http://docs.angularjs.org/api/ng/service/$http
//Same sample with handler functions moved out for ease of reading etc.
var successFunction = function(res){
}
var errorFunction = function(res) {
}
var params = {
subjectId:$scope.config.subjectId,
examId:$scope.config.examId,
userId:$scope.config.createdById
};
$http.post("/api/Test/Retrieve", params).
success(successFunction).
error(errorFunction);

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);
}
};
}

Resources