I'm calling the following service:
Get accounts details
First piece of code in my controller:
//resp is coming from an $http sync request, above the below request.
for (var i = 0; i < resp.rows; ++i) {
userService.getAccountsBalance($rootScope.userid, resp[i]['Acct_Number']).then(function(data){
console.log(data.bal) // shows two duplicate balances
});
}
In my service:
app.service('userService', function($rootScope, $q){
var deferred = $q.defer();
this.getAccountsBalance = function(userid, accountNum){
console.log(userid + " " + accountNum)
var req = <my $http request>
req.send().then(function(resp){
deferred.resolve(resp.responseJSON);
});
console.log(deferred.promise) //// prints two balances JSON objects with no duplicate
return deferred.promise;
}
});
My question is, I can see that two requests(with different parameter) has been executed in my service, and returning two different balances for two accounts. However, in my controller I get two duplicate results. I only get the last response twice.
I'm pretty sure it has something to do with promises, and I'm still new.
The problem seems to be you are returning the same promise for both requests,
app.service('userService', function($rootScope, $q){
this.getAccountsBalance = function(userid, accountNum){
var deferred = $q.defer();
Change it like above and it should work.
In order to loop through promises, you should use the $q service which provide the $.all method that allows you to resolve only when all promises has been resolved, and then get the data for each promise
When there are multiple promises are involved,if anyone promise get resolved all promise will get resolved with same value.
So you should use $q.all() to get result of all promises.
You can do in this way.
Inject $q Service.
var arrAllPromise = [];
for (var i = 0; i < resp.rows; ++i) {
arrAllPromise[i] = userService.getAccountsBalance($rootScope.userid, resp[i]['Acct_Number'])
}
$q.all(arrAllPromise).then(function(data) {
console.log(data) // shows two duplicate balances
});
It will give all promise data with which it is resolved.
Basically problem was you were having common $q object for promise.When your 1st promise gets resolved, the outer $q promiseis resolved by some response.
Then you had 2nd call for the same function, it would make that $http call but return old resolved promise. Because old $q object has fulfilled its promise already.
For solving the problem, you should have separate $q.defer() inside method. So that each time proper data will return by service method
You are creating a custom promise using $q,which is considered as bad pattern. As you have $http.get method is there in place, you can utilize their promise(each $http method promise, and we can chain them using .then)
Service
app.service('userService', function($rootScope, $q){
this.getAccountsBalance = function(userid, accountNum){
var req = <my $http request>
//utilize promise object returned by $http
req.send().then(function(resp){
//return data that sent to promise chain function
return resp.data;
});
}
});
Related
I'm using $resource for making restful request to the server.
My code is
service.js
.factory('userServ',function($resource){
return $resource('url/:id',{id:'#id'},{
me: 'GET',isArray:false,url:'url2'}
});
})
I'm using this service in the controller like this
$scope.userDetails = userService.me();
console.log($scope.userDetails) // gives all the details of the user
console.log($scope.userDetails.username) // gives undefined. But username is there in the object
So how can I access that from the object
Your resource object returns a promise. Read more about it here: https://docs.angularjs.org/api/ngResource/service/$resource
var myResource = $resource('url/:id',{id:'#id'},{
me: 'GET',isArray:false,url:'url2'}
});
In order to get the data out of it you need to call the methods of the promise itself. They are GET, PUT and SAVE.
But here you only have a GET mentioned there.
So if you resolve your promise by calling
var myResult = [];
myResource.then(function(data){
myresult = date;
});
Now, 'myResult' here will have your returned data.
It can also be called like this ():
.get({userId:123})
.$promise.then(function(user) {
$scope.user = user;
});
Again I would recommend reading the angular docs to get a better understanding.
In your aprroach, promise is not resolved. So you are not able to naccess data.
Here,
$scope.userDetails = userService.me();
Promise is not resolved. when you expand your Object you will see resolved:false. Although you will be able to see data inside $scope.userDetails you needed.
So, first you need to wait till promise is resolved.For this you can use .then method.
var CallApi = userService.me().$promise; //Promise yet to be Resolved
CallApi.then(function (res)){ //Promise resolved here
$scope.userDetails = res;
console.log($scope.userDetails)
console.log($scope.userDetails.username)
}
})
my angularjs code is as follows:
holidays = new Array();
var deferred = $q.defer();
servicePOST.send(appConstants.BASE_MS_URL + 'Dcrs/activityDay.php',{
"date":d
}).then(function(result) {
$scope.holidays = result;
alert(holidays.length);
deferred.resolve(result);
});
return deferred;
alert("holiday length after service post"+holidays.length);
angularjs is not sequential as it occurs to me. so this code executes in the end! this code of mine is inserted in such an order in my angularjs controller that the code down below depends on this 'holidays' array. what happens in my above code is the alert 'holiday length after service post0' pops up first and than after service post executes in the end, the length of the array is actually displayed.
now i tried using $q,promises,deffered as above but it doesn't seem to work.i have injected $q only in my angularjs controller.
is there anything wrong i have done in the above code? please help!
A promise returns another promise, so you can use this 2nd promise to have your logic performed in the order you wish. You are also complicating things in this situation with the $q service.
holidays = new Array();
var promise = servicePOST.send(appConstants.BASE_MS_URL + 'Dcrs/activityDay.php',{
"date":d
}).then(function(result) {
$scope.holidays = result;
alert(holidays.length);
return result;
});
promise.then(function(result) {
alert("holiday length after service post"+holidays.length);
});
The code is executed sequentially, it's just that the request takes time to resolve, but it starts before the alert. You should handle the request first, then alert the result inside the "then".
I still can't understand the role of using the $q service, (what exactly will it add) if you want to create a service that need to call only one API via http , in this situation I don't know why shouldn't I just do the following (without using $q) :
this.getMovie = function(movie) {
return $http.get('/api/v1/movies/' + movie)
.then(
function(response) {
return {
title: response.data.title,
cost: response.data.price
});
},
function(httpError) {
// translate the error
throw httpError.status + " : " +
httpError.data;
});
};
Very good question and one very few people appreciate the answer to.
Consider this:
this.getMovie = function(movie) {
return $http.get('/api/v1/movies/' + movie);
};
Great code but these rules apply:
$http will resolve for 2xx responses and will otherwise reject. sometimes we don't want this. we want to reject on resolution and resolve on rejection. This makes sense when you consider a HEAD request to check the existence of something.
A HEAD request to /book/fred returning 200 shows book fred exists. But if my function is testing whether book fred is unique, it is not and so we want to reject on a 200. this is where $q comes in. Now I can do this:
var defer = $q.defer();
$http.head('/book/fred').then(function(response) {
// 2xx response so reject because its not unique
defer.reject(response);
}).catch(function(failResponse) {
defer.resolve(failResponse);
});
return defer.promise;
$q gives me total control of when I reject AND when I resolve.
Also, $q allows me to reject or resolve with any value. So if I am only interested in some of the response I can resolve with just the bit I am interested in.
Finally, I can use $q to turn non-promise based code into a promise.
var a = 5;
var b = 10;
var defer = $q.defer();
var resolve(a+b);
return defer.promise;
Bosh, if I need a promise as my return value then I've got one.
This is also great when mocking for unit tests.
AngularJS services such as $http, $timeout, $resource, etc. use the $q service internally to generate their promises. With those services there generally is no need to inject the $q service. In fact if you see $q.defer being used with those services, you should be asking Is this a “Deferred Antipattern”?.
There are some methods of the $q service that are useful in certain circumstances.
The $q.all method is used to wait on several promises.
var promise1 = $http.get(url1);
var promise2 = $http.get(url2);
$q.all([promise1, promise2]).then( responseArray ) {
$scope.data1 = responseArray[0].data;
$scope.data2 = responseArray[1].data;
}).catch( function ( firstError ) {
console.log(firstError.status)
});
The .catch method can be used to convert a rejected promise to a fulfilled promise. And vice versa with the .then method. No need to use $q.defer for that. For more information, see Angular execution order with $q.
The $q.when method is useful for generating a promise from unknown sources.
var promise = $q.when(ambiguousAPI(arg1));
The $q.when method creates a $q promise in all cases whether ambiguousAPI returns a value, a $q promise, or a promise from another library.
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.1
To summarize: The $q service is used to create a promise, so when using service (like $http,$timeout,$resource,etc.) that already return promises you generally don't need to use the $q service.
In this case you certainly don't need it because $http.get itself returns a promise. But for example if you perform async call only on some condition it is useful
function acyncService () {
if (dataLoaded) return $q.resolve(data);
return $http.get('path/to/load/data');
}
In this case even if you do not perform async call, you still can use
acyncService().then(function(data){
console.log(data);
});
This is only one of many examples. It is also useful to use $q promises when you do async requests with other libs like AWS SDK for instance.
$http does an asynchronous call. So, ideally if you want to get the value of the response in the url, you should use a '$scope' variable to store it.
$http("your url").then(function(response){
//This is the success callback
$scope.movies = response.data;
});
I want to create a service that returns a json
Or by request to to the server, or by checking if it exists already in: Window.content
But I don't want to get a promise from my Controller !
I want to get the json ready !
I have tried several times in several ways
I tried to use with then method to do the test in my Service
but I still get a promise
( Whether with $http only, and whether with $q )
I could not get the value without getting promise from my Controller
My Service :
app.service('getContent',['$http', function( $http ){
return function(url){ // Getting utl
if(window.content){ // if it's the first loading, then there is a content here
var temp = window.content;
window.content = undefined;
return temp;
}
return $http.get(url);
};
}]);
My Controller:
.state('pages', {
url: '/:page',
templateProvider:['$templateRequest',
function($templateRequest){
return $templateRequest(BASE_URL + 'assets/angularTemplates/pages.html');
}],
controller: function($scope, $stateParams, getContent){
// Here I want to to get a json ready :
$scope.contentPage = getContent(BASE_URL + $stateParams.page + '?angular=pageName');
}
});
If the data exists, just resolve it in a promise.
While this process is still asynchronous it won't require a network call and returns quickly.
app.service('getContent',['$http', '$q', function( $http, $q ){
return function(url){
// create a deferred
var deferred = $q.defer();
if(window.content){ // if it's the first loading, then there is a content here
var temp = window.content;
window.content = undefined;
deferred.resolve(temp); // resolve the data
return deferred.promise; // return a promise
}
// if not, make a network call
return $http.get(url);
};
}]);
Just to reiterate, this asynchronous, but it won't require a network call.
This is not possible. If the code responsible to calculate or retrieve the value relies on a promise, you will not be able to return the value extracted from the promise by your function.
Explanation: This can easily be seen from the control flow. A promise is evaluated asynchronously. It may take several seconds to retrieve json from a server, but the caller of your function should not wait so long because your whole runtime environment would block. This is why you use promises in the first place. Promises are just a nice way to organize callbacks. So when your promise returns, the event that caused the function call will have already terminated. In fact it must have, otherwise your promise could not be evaluated.
You're thinking about this wrong. A service always returns a promise, because there is no synchronous way of getting JSON from an API:
app.factory('myService', ['$http', function($http) {
return $http('http://my_api.com/json', function(resp) {
return resp.data;
});
}]);
You would then call this within your controller like so:
app.controller('myController', ['$scope', 'myService', function($scope, myService) {
myService.then(function(data) {
$scope.contentPage = data; // here is your JSON
}, function(error) {
// Handle errors
});
}]);
Your service is returning a promise as it's written at the moment. A promise is always a promise, because you don't really know when it will be finished. However with Angular's 2 way data binding this isn't an issue. See my edits bellow as well as the example on $HTTP in the docs
In your controller
controller: function($scope, $stateParams, getContent){
getContent(BASE_URL + $stateParams.page + '?angular=pageName')
.then(aSuccessFn, aFailedFn);
function aSuccessFn(response) {
// work with data object, if the need to be accessed in your template, set you scope in the aSuccessFn.
$scope.contentPage = response.data;
}
function aFailedFn(data) {
// foo bar error handling.
}
}
I seem to be having an issue sending json from my factory out to controllers
Here is my factory
.factory("UserService", function($http) {
var LevelsHere;
$http.get("/assets/images/generated.json").success(function(data){
LevelsHere = data;
return LevelsHere;
});
return {
all: function() {
return LevelsHere;
},
first: function() {
return LevelsHere[0];
}
};
})
I am simply trying to send the json object out (or bits of it) with this factory. I can console.log inside the http get and it seems to be grabbing the json just fine. I seem to have hit a wall, any help would be much appreciated. I would just like the all ad first functions to be working. thanks!
I first had success by hard coding the levelsHere above it with the json string like var levelsHere = [{"stuff in here"}], but when i moved it over to an $http it doesn't work.
Since you don't have any $watch to look over the value returned from asynchronous $http.get request, the updated value is not available to the consumer. As $http.get request returns a promise, you can leverage the promise and update the value on success of the promise in then() as below:
var app = angular.module('app', [])
.factory("UserService", function($http) {
var LevelsHere = $http.get("https://api.github.com/users/mralexgray/repos")
.success(function(data){
return data;
});
return {
all: function() {
return LevelsHere;
}
};
})
.controller('controller', function(UserService, $scope){
UserService.all().then(function(data){
$scope.value = data;
});
})
DEMO
What is not working exactly? My guess is that you got undefined immediately after this factory method, since $http uses deferred object
You are returning LevelsHere before the async call is finished. The order of your operation goes:
call http.get
return all and first which return LevelsHere (even though the http request has not finished)
http get returns json
success call back fires returning LevelsHere to nobody.
A better way is to just return the promise:
return $http.get("/assets/images/generated.json")
then in your controller you can get the value from the promise by calling the success function. If you try to resolve the promise in the factory and return the value, your controller will try to use the value before it's returned from the server.
var promise = UserService()
promise.success(function(data){
// do something with data
}