Angular $resources Cancel requests : $q - angularjs

I'm trying to understand...
How can I implement a requests cancell for this kind of services.
I was reading, that I shoud use $q.defer()
angular.module('App').service('TService', function ($resource, portal) {
return $resource(portal.getUrlServer() + 'api/T/:id', { id: '#Id' }, {
T_GET: {
method: 'GET',
params:{
id_turno: '#id_turno',
},
url: portal.getUrlServer() + 'api/T/T_GET/'
},
G_GET_Turno: {
method: 'GET',
params: {
id_tramite_relevado : '#Id_Tramite_Relevado'
},
url: portal.getUrlServer() + 'api/T/G_GET_Turno/'
},
});
What I want to do is when any method is called twice, I just want to let run the last called and cancel the other requests.

From AngularJS docs:
If an action's configuration specifies that it is cancellable, you can cancel the request related to an instance or collection (as long as it is a result of a "non-instance" call).
// ...defining the `Hotel` resource...
var Hotel = $resource('/api/hotel/:id', {id: '#id'}, {
// Let's make the `query()` method cancellable
query: {method: 'get', isArray: true, cancellable: true}
});
// ...somewhere in the PlanVacationController...
...
this.onDestinationChanged = function onDestinationChanged(destination) {
// We don't care about any pending request for hotels
// in a different destination any more
this.availableHotels.$cancelRequest();
// Let's query for hotels in '<destination>'
// (calls: /api/hotel?location=<destination>)
this.availableHotels = Hotel.query({location: destination});
};

Related

AngularJS Trigger callback after ForEach

I'm currently developing an AngularJS form which on submit pushes single or multiple participant data to an SQL database.
I'm able to push data to the SQL database, but I'm wanting to trigger a callback that redirects the user once all participant data has been successfully submitted.
At the moment on success it redirects the user but, misses the next foreach submit for the next participant.
Any and all advice would be appreciated.
AngularJS
/* Submit */
$scope.submit = function() {
var array = $scope.form.participants;
//console.log(array);
angular.forEach(array, function(value, key){
var request = $http({
method: "post",
url: 'http://xxx.co.uk/submit.php',
data: {
coachName: $scope.form.program.coachName,
contactArea: $scope.form.program.contractArea,
postcode: $scope.form.program.postcode,
programmeStart: $scope.form.program.programmeDate,
sessionDate: $scope.form.program.sessionDate,
sessionNumber: $scope.form.program.sessionNumber,
weekNumber: $scope.form.program.weekNumber,
id: value.participant.id,
attendance: value.participant.attendance,
weight: value.participant.weight,
goldBehaviours: value.participant.goldBehaviours,
stepCount: value.participant.stepCount,
creditData: value.participant.creditData,
weekOne: value.participant.weekOne,
stepOne: value.participant.stepOne,
weekTwo: value.participant.weekTwo,
stepTwo: value.participant.stepTwo,
weekThree: value.participant.weekThree,
stepThree: value.participant.stepThree,
weekFour: value.participant.weekFour,
stepFour: value.participant.stepFour,
weekFive: value.participant.weekFive,
stepFive: value.participant.stepFive
},
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
}).success(function(data) {
//console.log(data);
$location.path("/thankyou");
});
});
};
Data
{
"program":{
"coachName":"AD",
"contractArea":"Berkshire",
"postcode":"NN1",
"programmeDate":"2016-08-15T23:00:00.000Z",
"sessionDate":"2016-08-16T23:00:00.000Z",
"sessionNumber":"1",
"weekNumber":"2"
},"participants":[
{"participant":{"id":"AW01","attendance":"Did Not Attend","weight":"1","goldBehaviours":"2","stepCount":"3","creditData":"","weekOne":"4","stepOne":"4","weekTwo":"5","stepTwo":"5","weekThree":"6","stepThree":"6","weekFour":"7","stepFour":"7","weekFive":"8","stepFive":"8"}},
{"participant":{"id":"AW02","attendance":"Attended","weight":"2","goldBehaviours":"3","stepCount":"4","creditData":"","weekOne":"5","stepOne":"5","weekTwo":"6","stepTwo":"6","weekThree":"7","stepThree":"7","weekFour":"8","stepFour":"8","weekFive":"9","stepFive":"9"}}
]
}
You can inject $q to your controller/service and use $q.all method (you can also use native Javascript Promise if you're not worried about old browsers support).
The all method takes an array of promises and resolve when all promises in the array resolve (it will reject if any of the promises reject).
$scope.submit = function() {
var array = $scope.form.participants;
var promises = [];
//console.log(array);
angular.forEach(array, function(value, key){
promises.push($http({
method: "post",
url: 'http://xxx.co.uk/submit.php',
data: {
coachName: $scope.form.program.coachName,
...
...
...
stepFive: value.participant.stepFive
},
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
}));
});
$q.all(promises).then(function() {
$location.path("/thankyou");
});
};
You are telling it to redirect with each iteration, not after all iterations have been completed. Try moving your redirect like so:
angular.forEach(array, function(value, key){
var request = $http({
method: "post",
url: 'http://xxx.co.uk/submit.php',
data: {
coachName: $scope.form.program.coachName,
contactArea: $scope.form.program.contractArea,
postcode: $scope.form.program.postcode,
programmeStart: $scope.form.program.programmeDate,
sessionDate: $scope.form.program.sessionDate,
sessionNumber: $scope.form.program.sessionNumber,
weekNumber: $scope.form.program.weekNumber,
id: value.participant.id,
attendance: value.participant.attendance,
weight: value.participant.weight,
goldBehaviours: value.participant.goldBehaviours,
stepCount: value.participant.stepCount,
creditData: value.participant.creditData,
weekOne: value.participant.weekOne,
stepOne: value.participant.stepOne,
weekTwo: value.participant.weekTwo,
stepTwo: value.participant.stepTwo,
weekThree: value.participant.weekThree,
stepThree: value.participant.stepThree,
weekFour: value.participant.weekFour,
stepFour: value.participant.stepFour,
weekFive: value.participant.weekFive,
stepFive: value.participant.stepFive
},
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
}).success(function(data) {
//console.log(data);
});
$location.path("/thankyou");
});
Your redirect needs to be outside of your forEach.

Building one object with only one $resource

I'm building a fullrest app with $resources, I read about It but I didn't find any answer.
return $resource('/rings', {}, {
getRings: {
method: 'GET',
isArray: true
},
patchRing: {
method: 'PATCH',
params: {
slug: '#slug'
}
}
}
Get Rings is doing ok, but How can I "patchRing"? I mean I want to PATCH for endpoint: /rings/:slug Is this possible? or Do I need another $resource for that (like next one)?
return $resource('/rings/:slug', { slug: '#slug'}, { [...]
EDIT: I don't want the "PATCH" like this /rings?slug=lorem just /rings/lorem
EDIT 2: My point is only the endpoint construction... because $resource is requesting to /rings?slug=lorem instead of build request like /rings/lorem
Try this in your config
$resourceProvider.defaults.stripTrailingSlashes = true;
This will not end as /
Try putting the param in the $resource definition instead of the PATCH method:
var Ring = $resource('/rings/:slug', {slug: '#slug'}, {
getRings: {
method: 'GET',
isArray: true
},
patchRing: {
method: 'PATCH',
}
});
ring = Ring.patch({slug: 'lorem'}, function() { ... });
If the slug parameter is not passed, then it is not added to the HTTP path.

Restangular save and retry after refresh

I'm working on an SPA that is usually online but can go offline and I have to keep a log of all requests to an API. When certain requests fail I should retry them later.
I've got a local DB to save all requests. When my app starts I retrieve them all and retry the ones marked to.
What I need is a way to config a Restangular object based on what I already sent. I have a response interceptor and I'm saving the restangular response object.
{
config: {
headers: Object,
method: "GET",
params: Object,
transformRequest: Array[1],
transformResponse: Array[1],
url: "..."
},
data: {...},
headers: {...},
status: 200,
statusText: "OK"
}
Is there a function to create a Restangular with the given config object?
Thanks
If i would doing this i would setup setErrorInterceptor
var yourLocalDb = function($http) {
var failedRequests = [];
this.store = function(request) {
this.failedRequests.push(request);
}
this.retry = function() {
var self = this;
angular.forEach(this.failedRequests,function(request) {
$http(request.response.config).then(function() {
//in case of success
self.failedRequests.splice(self.failedRequests.indexOf(request),1);
request.responseHandler.apply(undefined,arguments);
}), request.deferred.reject);
});
}
}
Restangular.setErrorInterceptor(function(response, deferred, responseHandler) {
yourLocalDb.store({
response : response,
responseHandler : responseHandler,
deffered : deffered
});
}
then when you have connection you can just call
yourLocalDb.retry();
Not tested, but it should give you clue.

Invalidate $resource Cache After Post Request

I am using $resource and caching the results of get requests. My problem is that, after post requests, the cache is not being invalidated.
Here is the return value from the service:
return $resource('http://url.com/api/url/:id', {}, {
'query' : {
method : 'GET',
isArray:true,
cache : true
},
'get' : {
method : 'GET',
cache : false
}
})
Here is the save method I am using inside my controller. As you can see, I'm using the callback on the post request to recalculate the query/list of nouns.
var newNoun = new Noun($scope.noun);
newNoun.$save(function(x) {
$scope.nouns = Noun.query();
});
I would like to invalidate the cache after calling post or another non-get method. How could I do this? Is this already built into $resource or do I need to implement it on my own?
You could create a wrapper service to do the caching like you want, for example:
app.factory('cachedResource', function ($resource, $cacheFactory) {
var cache = $cacheFactory('resourceCache');
var interceptor = {
response: function (response) {
cache.remove(response.config.url);
console.log('cache removed', response.config.url);
return response;
}
};
return function (url, paramDefaults, actions, options) {
actions = angular.extend({}, actions, {
'get': { method: 'GET', cache: cache },
'query': { method: 'GET', cache: cache, isArray: true },
'save': { method: 'POST', interceptor: interceptor },
'remove': { method: 'DELETE', interceptor: interceptor },
'delete': { method: 'DELETE', interceptor: interceptor },
});
return $resource(url, paramDefaults, actions, options);
};
});
Then replace any $resource with cachedResource.
Example plunker: http://plnkr.co/edit/lIQw4uogcoMpcuHTWy2U?p=preview
While #runTarm's answer above is great, it does not allow actions to be easily customized from the inheriting service, e.g. the following would not be possible:
app.factory('Steps', function (CachedResource) {
return CachedResource('/steps/:stepId', {}, {
save: { method: 'POST', params: { stepId: '#stepId' } }
});
});
In this case, this definition of save would be replaced by the one present in CachedResource.
Solution
But it can be fixed easily from Angular 1.4 by replacing
actions = angular.extend({}, actions, {
with
actions = angular.merge({}, actions, {
so that both objects are deep-merged.
Even better solution
In the above scenario, action options defined in CachedResource would be preferred over custom configuration in inheriting services. To fix that, switch the order of arguments passed to merge:
actions = angular.merge({}, { /* default options get, query, etc. */ }, actions);
With this solution, the following will work as expected (i.e. use DESTROY instead of default DELETE when calling remove):
app.factory('Steps', function (CachedResource) {
return CachedResource('/steps/:stepId', {}, {
remove: { method: 'DESTROY' }
});
});
$resource is using the default cache for $http.
You can access it using: $cacheFactory.get('$http')
You can remove a key value pair, using the returned caches remove({string} key) method.
E.g.:
var key = '...the key you want to remove, e.g. `/nouns/5`...';
$cacheFactory.get('$http').remove(key);

Can someone give me an example on how I can call $resource directly?

In my code I have:
var EntityResource = $resource('/api/:entityType', {}, {
postEntity: { url: '/api/:entityType/', method: 'POST' },
getEntity: { url: '/api/:entityType/:entityId', method: 'GET' },
putEntity: { url: '/api/:entityType/:entityId', method: 'PUT' },
deleteEntity: { url: '/api/:entityType/:entityId', method: "DELETE" },
getEntities: { url: '/api/:entityType/:action/:id', method: 'GET', isArray: true },
});
Then I am using the following to get data:
getProjects: function (
entityType,
deptId) {
var deferred = $q.defer();
EntityResource.getEntities({
action: "GetProjects",
entityType: entityType,
deptId: deptId
},
function (resp) {
deferred.resolve(resp);
}
);
return deferred.promise;
},
and the following to call getProjects:
entityService.getProjects(
'Project',
$scope.option.selectedDept)
.then(function (result) {
$scope.grid.data = result;
}, function (result) {
$scope.grid.data = null;
});
I think the intermediate function getProjects is not needed and I would like to directly use $resource.
Can someone give me some advice on how I could do this? I looked at the AngularJS documentation for $resource and it's not very clear for me.
$resource calls by default return empty arrays and then fill them up when the response is received. As mentioned in documentation
It is important to realize that invoking a $resource object method
immediately returns an empty reference (object or array depending on
isArray). Once the data is returned from the server the existing
reference is populated with the actual data.
There are default 5 methods already defined on resource, get,save,query,remove,delete. You can directly call these rather than defining your own as you have done like postEntity, but the url template remains the same.
So once you define resource like this
var entityResource = $resource('/api/:entityType');
you can make calls like
var entity=entityResource.get({entityType:1},function(data) {
//The entity would be filled now
});
See the User example in documentation
If you want to return promise then you have to wrap the calls into your your service calls like you did for getProjects.
Update: Based on your comment, the definition could be
var entityResource = $resource('/api/:entityType/:action/:id')
Now if you do
entityResource.get({},function(){}) // The query is to /api
entityResource.get({entityType:'et'},function(){}) // The query is to /api/et
entityResource.get({entityType:'et',:action:'a'},function(){}) // The query is to /api/et/a
entityResource.get({entityType:'et',:action:'a',id:1},function(){}) // The query is to /api/et/a/1
Hope it helps.
$resource does expose $promise but it is on return values and subsequent calls.

Resources