How do i deal with server pagination with ngResource? - angularjs

My scenario is that I want to use ngResource to retrieve lists of files from Google Drive. Drive paginates its results via the inclusion of a nextPageToken within the JSON response, null signifying no more results.
I need a promise or callback that refers to the completion of all pages, rather than after each page. Is there an elegant way for ngResource to handle this?

I don't think ngResource will inherently do anything like this for you by default, but you can do it by using a recursive closure and calling the ngResource inside it until you get all the data you are after.
var dataSet = [];
function getAllData(){
var files = $resource('your url');
files.get(function(data){
if (data){
dataSet.push(data); //or whatever
getAllData();
}
})();
}
Here is the tested version with actual Google Drive semantics
getAllFiles(nextLink:string){
var self = this;
if (nextLink) {
var files = self.refs.resource(nextLink);
} else {
var files = self.refs.resource(this.DRIVE_URL);
}
files.get(function(data){
console.log("fetched "+data.items.length);
self.allFiles.push(data['items']);
if (data['nextLink']){
self.getAllFiles(data['nextLink']);
}
});
}

Related

How to stop double request to same url

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

Angular translate - When multiple loader it execute only the last one

I've got a project using angular translate with a custom loader.
Basically, this is the config in my provider (which is working perfectly).
Provider (stuff executed in the config of my app)
$translateProvider.useSanitizeValueStrategy('sanitize');
$translateProvider.useLoader('componentsTranslationLoader');
$translateProvider.preferredLanguage($language);
As you can see, I use my own componentsTranslationLoader. It does the stuff as expected.
Factory (componentsTranslationLoader)
return function(options) {
var deferred = $q.defer();
var translations = {};
$http.get('languages/components/' + options.key + '.json').success(function(keys) {
translations = keys;
deferred.resolve(translations);
});
return deferred.promise;
};
Everythings is fine from here.
I have to use a library in this project (company's one, I can edit it), and this library also has his own angular translate stuff (basically the same thing).
It has a custom loader, initialized into the config.
When my project is executed, I expect that both loader do their stuff and extend the language with their keys.
It didn't.
Only the last loader is executed (see it with logs).
So, how can I resolve this conflict properly please ?
Is there something wrong with my way of using angular translate ?
Thanks for the help guys.
Edit (more informations added)
I added more call like this one into the config with different 'fake' loader:
$translateProvider.useLoader('aFakeLoaderWithLogs');
And the problem still the same, only the last one into the config is called.
I searched for topics with similar issues and found nothing, even in the documentation.
Try this approach of merging responses. Works for me very well.
function customLoader($http, $q, localeUrl, errorCodeUrl) {
return function (options) {
var deferred = $q.defer();
var translations = [];
$q.all([
$http.get(localeUrl + "locale-" + options.key +".json"),
$http.get(errorCodeUrl + "?lang=cs")
]).then(function(response, status) {
translations.push(response[0].data);
translations.push(response[1].data);
console.log(translations);
deferred.resolve(translations);
return translations;
});
return deferred.promise;
};
}

How can i make multiple http requests in a factory

I was wondering if we can load multiple json files using a factory and a controller.
Im pulling classifieds.json and myWords.json.
Im seeing content from the former, but the content from the latter is not being displayed
This is how i tired to incorporate it. I've checked the propriety of myWords.json against a json formatter, so i know for sure that its all right.
I guess, im doin sthg wrong here. Id appreciate if you could guide me in the right direction.
Factory
Controller
In order to fetch data from multiple sources, the currect way is to use one method for each data source.
Factory:
function getClassified() {
return $http.get('data/classified.json');
}
function getMyWords() {
return $http.get('data/myWords.json');
}
return {
getClassified : getClassified,
getMyWords : getMyWords
}
Controller
classifiedsFactory.getClassified().then(function(data) {
$scope.classified = data;
};
classifiedsFactory.getMyWords().then(function(data) {
$scope.myWords = data;
}
If you have more than 2 source that you want to get together, you can use $q service in your factory:
function getAllData() {
var source1 = $http.get('source1.json');
var source2 = $http.get('source2.json');
...
...
return $q.all([source1, source2, ...]);
}
This will resolve only when all data have been recieved and you can get it in your controller.
Don't forget to include $q in your factory function dependencies
Demo link
Hi Ashim09,
Here I added sample for multiple json call in factory..

Passing and retrieving complex objects from Angularjs to Web Api

I have a form in angular where a user enters various criteria which I then want to pass to Web Api and get a result after queries are run. I originally thought of this as a "Get" but had trouble passing complex objects to the Web Api. With some advice, I then used a Post and was able to pass the criteria run the query in the Web Api but I had trouble getting the result back in Angular. The Web Api method is run and gets the results. But I don't see the results in the data service.
What is the best approach where the criteria for a query is multiple fields and some are lists? I haven't been able to find any good examples.
Here is the Web Api method:
[HttpPost]
public IEnumerable Post([FromBody] FrequentPawnerReportCriteria criteria)
{
var repo = new FrequentPawnerReport();
var result = repo.GetReport(criteria);
return result;
}`
Here is the dataservice:
function getFrequentPawner(criteria) {
return $http.post("/api/FrequentPawner/Post", criteria)
.then (getFrequentPawnerComplete)
.catch(getFrequentPawnerFailed);
function getFrequentPawnerComplete(response) {
var x = response
return response.data.results;
}
function getFrequentPawnerFailed(error) {
alert("XHR failed for frequent pawner report: " + error.responseText);
}
}
And here is the controller code:
function getTopPawnerResults(criteria) {
return DataContext.getFrequentPawner(criteria)
.then(
function (result) {
vm.frequentPawnerReport = result.data;
return vm.frequentPawnerReport;
});
}
Simply use JSON. Use JSON.stringify() to parse JSON object to string and POST it. Similarly, return JSON string from server, and assign it to variable in Angular. It will be automatically converted to JSON object.
I think when you make your post request, you need to have a callback function that would get invoked when your Web Api returns. Within that callback function you can update your $scope variables which will make your web ui show the response from the server. You can find an example of what I mean here: https://docs.angularjs.org/api/ng/service/$http
The gist of it:
$http({
method: 'POST',
url: '/path/to/your/web/api',
function(success) {
console.log('Successfully executed the api call');
$scope.response = response; // change this to match the data you are expecting from the server response
},
function(failure) {
console.error('There was an error');
$scope.failure = failure; // change this to match your failure response
}
);
Thanks for responding. The project is a mix of web forms and angularjs. I am migrating the app and didn't notice this form had a conflict which was causing a post back and making it look like the result was not being returned. I took the form into a separate project and was able to get the results I was going for.

AngularJS preprocess specific $http requests

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.

Resources