How to use restangular with promise pattern in angular service - angularjs

I have a service with rest angular with following structure
function countrySvc(restangular) {
restangular.addResponseInterceptor(function (data, operation, what, url, response, deferred) {
if (operation === 'getList') {
var newResponse = response.data;
return newResponse;
}
return response;
});
var baseCountry = restangular.all('country');
this.countries = function() {
baseCountry.getList();
};
}
also a controller
function countryCtrl(scope, countrySvc) {
scope.countries = countrySvc.countries();
}
but when i access the countries from controller, the result is empty with a successful request with data, my question is how a can extract the data from response with proper promise pattern, ie( i need array of countries when i access scope.countries)

You need to resolve promise...
There are two ways to do it...
1) Using $object
just add .$object to end of promise so once request is done it resolves promise...
scope.countries = countrySvc.countries().$object;
2) Using then
if you need to do some stuff after promise is resolved pick this option, once request is done callback function in then will be fired
scope.countries = countrySvc.countries().then(function (response){
// DO SOMETHING IF YOU NEED BEFORE SET OBJECT
scope.countries = response;
// DO SOMETHING IF YOU NEED AFTER SET OBJECT
});

Related

Http service delay processing until after data is returned

I work on my angularjs project.
I created this service:
(function () {
"use strict";
angular.module("manageItems").factory("manageItemsService", ["$http", "config", manageItemsService]);
function manageItemsService($http, config) {
var service = {
getNewItems: getNewItems,
};
return service;
function getNewItems(session, mapName) {
return $http.get(serviceUrl + 'getNewItems/' + session + "/" + mapName);
}
}
})();
And here how I call the service from controller:
function getNewItems() {
manageItemsService.getNewItems(mapguideService.mapName, mapguideService.sessionId).then(function (result) {
self.currentItems = result.data;
})
}
I need to make service to delay while the response returned.
How can I change servicefunction to make it wait until self.currentItems property is populated by data?
First I need say that http requests are actually performed asynchronously so as not to stop the application while the result is returned.
So you have two options, use the angular pattern to adjust your method in order to treat the result, so you must pass a callback function to the service, so that the service, not the controller make the association. It would be something like:
Service:
(function () {
"use strict";
angular.module("manageItems").factory("manageItemsService", ["$http", "config", manageItemsService]);
function manageItemsService($http, config) {
var service = {
getNewItems: getNewItems,
};
return service;
function getNewItems(session, mapName, callback, errorCallback) {
$http.get(serviceUrl + 'getNewItems/' + session + "/" + mapName).then(callback, errorCallback);;
}
}
})();
Controller:
function getNewItems() {
manageItemsService.getNewItems(mapguideService.mapName, mapguideService.sessionId, function (result) {
//this callback will be called asynchronously when the response is available
self.currentItems = result.data;
}, function(error) {
// called asynchronously if an error occurs
// or server returns response with an error status.
})
}
The second option is to totally not recommended, inserting a loop while the result is expected... (to bad)
I hope I have helped!
Then you could be put up .then on getNewItems $http call. And based on retrieved response data, decide whether to return data or call another service method.
function anotherFunction(){
return $http.get(url);
}
function getNewItems(session, mapName) {
return $http.get(serviceUrl + 'getNewItems/' + session + "/" + mapName).then(function successCallback(response){
var data = response.data;
//call another function if data is empty
if(!data.length)
return anotherFunction(); //make sure another function should return promise
return data;
});
}
What the code needs to do is chain promises.
To make the getNewItems function chainable, return the derived promise:
function getNewItems() {
//vvvv RETURN promise
return manageItemsService.getNewItems(mapguideService.mapName, mapguideService.sessionId)
.then(function (response) {
self.currentItems = response.data;
//RETURN value to chain
return response.data;
});
};
Then use the returned promise to chain more operations:
getNewItems().then( function(currentItems) {
//Evaluate current Items
if ( ok ) {
return "DONE";
} else {
//RETURN to chain something else
return getOtherItems();
};
}).then( function(otherItems) {
if (otherItems == "DONE") return;
//ELSE
self.otherItems = otherItems;
//Do further chaining
});
Because calling the .then method of a promise returns a new derived promise, it is easily possible to create a chain of promises.
It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs.
— AngularJS $q Service API Reference - Chaining Promises

AngularJS Http get empty first time

I have this code
PedidosService.getProductbyID($scope.listProductos.ProductID).then(function (d) {
$scope.oneProduct = d.data.producto;
});
PedidosService is the name of my factory, getProductbyId is my http get request and $scope.oneProduct is the variable where i want store the result of this request.
factory.getProductbyID = function (id) {
return $http.get('/Pedidos/GetProduct/' + id);
}
factory is my Factory and getProductbyID is my function to call http request
I call this code in a button. The first time that I click in the button, it returns a empty response '[]' but the next times that I click the button, it works fine!!!.
Thanks for you help
As far as I know the $http.get returns a promise because that call is asynchronous. With that in mind a typical call to $http.get should be something like this:
$http.get('/someUrl', config).then(successCallback, errorCallback);
In your context I would have done it this way, so the factory returns a promise and in your controllers you will handle the success and error callback appropriately:
factory.getProductbyID = function (id) {
var deffered = $q.defer();
$http.get('/Pedidos/GetProduct/'+ id)
.then(function (result) {
deffered.resolve(result);
}, function (data) {
deffered.reject(data);
});
return deffered.promise;
}

Is it okay to handle all the $http errors in controller?

In all my services, I'm just invoking REST services and returning the promises to the controllers. All the error's are handled at controllers using catch like below,
MyService.getData(url).then(getDataSuccess).catch(exception.catcher('Contact Admin : '));
My question here is, Since the real $http calls will be made at service, should I have to write catchers in service or catching in controller is fine?,
Scenario 1:
function getData(url){
return $http.get(url);
}
Scenario 2: (Nested calls to make combined results)
function getOtherData(url){
var defer = $q.defer();
$http.get(url).then(
function(response){
$http.get(nextService).then(
function(res){
defer.resolve('combined data');
}
)
}
);
return defer.promise;
}
Both the service method is not handling any errors. Instead it just returns the promise. Will there be any situation where this kind of exception handling will get failed?
Note: I have created decorators for handling javascript,angular errors and route errors separately. This question is particularly about $http service errors.
Yes what you have can fail triggering your catch because you have no reject().
You are using an anti-pattern creating your own promise and not chaining the nested request properly. Neither of those request rejections will be returned anywhere.
To be able to chain these get rid of the $q.defer() and do:
function getOtherData(url) {
// return beginning of promise chain
return $http.get(url).then(function (response) {
// return next promise
return $http.get(nextService).then(function (res) {
// combine and return the data
return {
d1 : response.data,
d2 : res.data
};
});
});
}
Now walk through the scenarios and each part of chain is intact.
Think of the chain as each then needs a return until the end of the chain
Scenario 2: (Nested calls to make combined results)
Failed Scenario
function getOtherData(url){
var defer = $q.defer();
$http.get(url).then(
function(response){
$http.get(nextService).then(
function(res){
defer.resolve('combined data');
}
)
}
);
return defer.promise;
}
This scenario will fail if the first $http.get has an error. The promise will hang and never get resolved. This is why we recommend avoiding using $q.defer to create promises from services that already return promises.
Instead return data and chain promises.
function getOtherData(url) {
var promise = $http.get(url);
var derivedPromise =
promise.then ( function (response) {
var data = response.data;
var nextPromise = $http.get(nextService);
var derivedNext = nextPromise.then(function(response) {
//return for chaining
return response.data;
});
//return composite for chaining
return $q.all([data, derivedNext]);
});
return derivedPromise;
};
The getOtherData(url) promise will be fulfilled with an array with the data from the two XHRs or it will be rejected with the first error response.
It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs.1
Chaining error handlers
In an error handler, to convert a rejected resolution to a fulfilled resolution return data. To chain a rejection, throw the error.
For example:
promise = http.get(someUrl);
derivedPromise = promise.catch(function(errorResponse) {
if (fixable) {
fixedPromise = $http.get(fixedUrl);
//return to convert
return fixedPromise;
} else {
//throw to chain rejection
throw errorResponse;
}
};
By chaining error handlers, errors can be handled both by the service and the client of the service.
This makes it possible to implement powerful APIs like $http's response interceptors.1
Building on #georgeawg's answer, if you want to return multiple sets of data then you don't need nested calls.
function getOtherData(url) {
var promise1 = $http.get(url).then ( function (response) {
return response.data;
});
var promise2 = $http.get(nextService).then(function(response) {
return response.data;
});
return $q.all([promise1, promise2]);
};
Now the caller gets a promise that resolves to a list of the 2 data items (or is rejected if either request fails). The only real difference is that both requests are issues in parallel.
This generalises easily to a situation where you could have a list of urls, fetch them all in parallel and get an array of the response.data items.
Because you get back only a single promise that resolves to an array of data you can handle the result in the controller, but you only need one error handler.
MyService.getOtherData(url)
.then(getDataSuccess)
.catch(exception.catcher('Contact Admin : '));
Although the original question doesn't specify, it might be the case that the second url depends on the result from the first. You can handle that case here as well if you remember that you can call .then() multiple times on the same promise:
function getOtherData(url) {
var promise1 = $http.get(url).then ( function (response) {
return response.data;
});
var promise2 = promise1.then(function(response) {
// compute nextService from response.data here...
var nextService = foo(response.data);
return $http.get(nextService).then(function(response) {
return response.data;
});
});
return $q.all([promise1, promise2]);
};

How to call Success or Error on binded methods (Controllers, Factorys)

I am trying to figure out how handle success and error on binded methods that return an http response.
I am trying to bind a factory method to a controller, so I am able to call it from the view. This method will try to add an item to the shopping cart via an http request. If it fails the method that is being called will return false, however if it succeeds it will return the http response.
I want to be able to somehow add success or error callbacks to the binded method. Is this possible?
Car controller
// Controller to display cars page
app.controller('carsController', function($scope, $rootScope, inventoryFactory) {
// Function that will fetch JSON and save all necessary data for us to use
function init() {
// Bind these method calls to our cart factory
// Allow method to be called from the view
$scope.addToCart = userFactory.addToCart;
// Get list of items in car category
inventoryFactory.getItems('cars').success( function(data) {
$scope.items = data;
});
}
init();
});
userFactory
// Add item of given ID to shopping cart
factory.addToCart = function(itemID) {
// Validate our user / token
data = factory.getUserToken();
if (data === false) {
return false;
}
req = {
method: 'PUT',
url: 'routes.php/api/shoppingcart/' + itemID,
headers: {
'X-Api-Token': data.apiToken,
'UserID': data.userID
}
};
return $http(req);
};
What you are doing will work, but I would recommend a few changes.
First, direct binding may cause problems. If your factory method at any point needs to call this, it will lose it, since this becomes the $scope rather than the returned object from the factory.
$scope.addToCart = userFactory.addToCart; // addToCart will have "this" be of $scope
Whereas you can keep it either by proxying or by wrapping:
$scope.addToCart = function(id) {
return userFactory.addToCart(id);
};
guarantees that addToCart inside the factory has the correct context for this.
Second, while you can do what you want, if you are returning a promise (as $http() does), then sometimes returning false and sometimes returning a promise can lead to messy code and difficulty testing. You may be better off always returning a promise and rejecting it as necessary:
var defer, ret;
data = factory.getUserToken();
if (data === false) {
defer = $q.defer();
defer.reject();
ret = defer.promise;
} else {
req = {
method: 'PUT',
url: 'routes.php/api/shoppingcart/' + itemID,
headers: {
'X-Api-Token': data.apiToken,
'UserID': data.userID
}
};
ret = $http(req);
}
return ret;
This way you will always have a promise, and can always do
addToCart(25).then(function(){/* success */),function(){/* failure */});
If you need to do error handling, you might want to handle it inside your controller. So if your template is:
<button ng-click="addToCart(item.id)">Click me!</button>
Then your controller addToClick might be:
$scope.addToCart = function(id) {
userFactory.addToCart(id).then(function(results){
// indicate success on the screen by changing some scope var, e.g.
$scope.message = "Successfully added to cart";
},function(err){
// indicate error on the screen by changing some scope var, e.g.
$scope.message = "Problem adding to cart: "+err;
});
};

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