There is related question about processing $http in a service, but I wanted to elaborate slightly. I would like my controller to be able to execute service calls using API similar to the Angular $http API:
$scope.login = function(user) {
securityService.login(user).success(function(data) {
$scope.data = data;
}).error(function(data) {
$scope.error = data;
});
};
That's a nice readable API. On the surface, all I would need to do in the service API is:
return {
name : 'User Service',
login : function(user) {
return $http.post("/api/login", user);
}
};
Great, it returns the promise and the success and error messages come with. But... What if I want to deal with the success and failure cases in the service? I want to maintain the nice, readable service API. In this case, maybe I'd want to preserve the user so that I could expose methods like securityService.currentUser() or `securityService.isLoggedIn()'.
I tried the $http.post().then(...) promise API, but those return the entire HTTP response. Again, I want to isolate HTTP to the services and maintain a similar concrete callback API.
You could make your own promise in login using angular's $q:
var deferred = $q.defer(),
promise = deferred.promise;
$http.post("/api/login", user).success(...).error(...)
return promise;
Within the success/failure of the $http promise in your service, resolve/reject your deferred object.
Edit:
Expanding upon the $http.post:
$http.post("/api/login", user).success(function(data) {
if (data == "foo") { // success case?
deferred.resolve('you logged in!');
} else {
deferred.reject('something really bad happened!');
}
})
I just ran a test case and it seems that if I return the promise from the service API like this:
return $http.post("/api/login", user).success(...).error(...)
I can also do the same in the controller:
service.login(user).success(...).error(...)
and both get called, the service first and the controller second. That's perfect!
Related
I have a service that (when it's all said and done) updates a value on the database.
I would like to update the view scope based on the result (success/fail) but of course the http request used by the service is asynchronous so the return value is not immediately available and comes up undefined in the controller.
If I were making the http request inside the controller, I would update the scope inside the callback function, but because Im using a service, the scope's umm... scope(?) is not available to it.
Im thinking a Promise is what I need to be returning but perhaps there is something more simple.
SERVICE
.service('doStuff',function($http){
this.update = function(data) {
$http.post('http://api.internet', data).then(function(res){
return(res.data.result);
});
}
})
CONTROLLLER
/* service is injected into controller etc. */
var result = doStuff.update(data);
p(result); // undefined (as expected)
I figured since Im returning from the http callback, it would wait for the result to be available before returning but I guess Im missing something.
Since $http is always async, you cannot return anything in the call back function. It is as good as not returning anything.
What you need to do is you need to return the $http promise, and then handle the callback functions in your controller.
Service:
.service('doStuff', function($http) {
this.update = function(data) {
return $http.post('http://api.internet', data);
}
})
Controller:
doStuff.update(data).then(function(result){
p(result);
});
Foremost, you need to return the query itself. Looks like
this.update = function(data) {
return $http.post('http://api.internet', data).then(function(res){
return(res.data.result);
});
}
Next step, you need get out of the promise function.
doStuff.update(data)
.then(function(res) {
//someone if request is success
})
.catch(function(rej) {
//someone if request is reject
});
Thanks in advance for the help...
I have a controller I'm using to call an API to send a password reset link. I have abstracted the actual $http call off into a service to keep the controller thin. Originally I was doing something like this:
angular.module('module')
.service('forgotPasswordService', ['$http', function($http) {
$http(request).then(function() {
return {}; //return some object using response
}]);
I felt like this would be the best approach as it would keep the controller as thin as possible and kept all service related actions separate. My problem with this was that returning from within the promise never actually returned me anything. I had to actually return the $http service call to get the promise. The only way I was able to make all of this work was if I called .then from within the controller.
//controller
angular.module('module')
.controller('forgotPasswordCtrl', ['forgotPasswordService', function(forgotPasswordService) {
forgotPasswordService.forgotPassword(emailAddress).then(function() {
//do stuff
}
}]);
//service
angular.module('module')
.service('forgotPasswordService', ['$http', function($http){
this.forgotPassword = function(emailAddress) {
return $http(request);
};
}]);
This just feels a little wrong to me because the controller now depends on receiving a promise back from the service. I may just be overthinking this but I would like a second opinion.
Is this considered acceptable/good practice? Is there an alternative to this which would allow me to achieve the encapsulation I'm looking for?
Thanks again.
I've interfaced with the $http from the controller in two slightly different ways.
Like you it felt wrong returning the $http from the service and interfacing with it.
So first I created services and passed in a success method and an error method (callbacks).
// Service
angular.module('module')
.service('forgotPasswordService', ['$http', function($http) {
function sendForgotPasswordEmail(emailAddress, success, error){
$http.post('/api/v1/resetpassword', {emailAddress:emailAddress})
.then(success, error);
}
return {
sendForgotPasswordEmail: sendForgotPasswordEmail
}
}]);
// Controller
angular.module('module')
.controller('forgotPasswordCtrl', ['forgotPasswordService', function(forgotPasswordService) {
forgotPasswordService.sendForgotPasswordEmail(emailAddress,
function(response){ //success
// notify user of success
},
function(response){ // error
// notify user of error
});
}]);
This worked great. I created an large application this way, but as I started on my second large angular project I wondered why I was hiding the $http's promise?
By passing back the promise, I can use other libraries that support promises. With my first approach I can't leverage other libraries promise support.
Passing back the $http promise
// Service
angular.module('module')
.service('forgotPasswordService', ['$http', function($http) {
function sendForgotPasswordEmail(emailAddress){
return $http.post('/api/v1/resetpassword', {emailAddress:emailAddress});
}
return {
sendForgotPasswordEmail: sendForgotPasswordEmail
}
}]);
// Controller
angular.module('module')
.controller('forgotPasswordCtrl', ['forgotPasswordService', function(forgotPasswordService) {
forgotPasswordService.sendForgotPasswordEmail(emailAddress)
.then(
function(response){ //success
// notify user of success
},
function(response){ // error
// notify user of error
});
}]);
I deleted my original answer, and I feel like a dork for stating that you could do it the other way. When I went back and checked my original code back when I first started angular, I found that I was calling then() twice in my application - once in my service where I returned the data, and once in my controller because calling $http(request).then() returns a promise.
The fact is, you're dealing with an asynchronous call. Suppose in your controller, you wanted to do this:
$scope.foo = myService.getFoo(); // No then()
The XmlHttpRequest inside the $http in getFoo() is an asynchronous call, meaning that it calls it and moves on. The synchronous option is deprecated. It's bad practice to make a blocking synchronous HTTP call because it will seize up your UI. This means you should use a callback when the data is ready, and the promise API is made just for that.
If you absolutely do not want to use the then() in your controller, I suppose you could probably pass your scope binding parameters to your service and let your service update them in your then call. I haven't tried this, and because it's asynchronous, I'm not sure if angular will know to call a digest() so you may need to call a $scope.$apply() if the values don't update. I don't like this, because I think the control of the values in the scope should be handled by the controller and not the service, but again - it's your personal preference.
Sorry for leading you astray with my initial answer - I ran into the same question you had, but when I looked back - I saw I used a silly solution.
-- Relevant statements in original answer --
Consider the following:
Where do you want your error handling for the call and who needs to know about it?
Do you need to handle specific failures in a particular controller or can they all be grouped together to one error handler? For some apps, I like to display the errors in a particular place rather than in a general modal dialog, but it's acceptable to create a service to handle all errors and pop them up for the user.
Where do you want to handle your progress/busy indicator?
If you have an interceptor wired up for all http calls and broadcasting an event to show/hide the busy indicator, then you don't need to worry about handling the promise in the controller. However some directives will use the promise to show a busy indicator which requires you to bind it to the scope in the controller.
To me, the decision is determined by the requirements and by personal choice.
Try using a callback like so:
angular.module('module')
.service('forgotPasswordService', ['$http', function($http) {
var recovery = function(request, callback) {
$http(request).then(function(response) {
callback(response);
})
}
return { recovery: recovery }
}]);
Then you would call it like this:
forgotPasswordService.recovery('http://...', function(response){
console.log(response);
})
So I am still on a crash course with Angular. I am working on quite a complicated dashboard framework, all written in angular. Before I load the controllers, I need to get a bunch of dashboard settings from the server first using $HTTP. These settings are then used to control the layout of the dashboards.
So I read the way angular builds is by first running config methods, then run methods, then the controllers.
I can't use $HTTP in a config method, so I have built this in my main.js:
MetronicApp.run(['$rootScope','$http', function($rootScope,$http) {
var CUID = Cookies("CUID");
console.log('portlet settings for '+ CUID);
$http.get('/myurl/V3_portlet_settings?p_user_id='+CUID)
.then(function(response) {
console.log(response.data);
console.log('portlet status: ' + response.status);
$rootScope.$broadcast("dashSettings",response.data);
});
}]);
When I run, this all works happily and I see the data in the console.
Then in my controller:
$scope.$on( "dashSettings",
function(event,data){
$scope.dData = data;
console.log('dash data service identified in dash controller');
console.log($scope.dData.count);
} );
Couple of questions:
Is this the best way to get settings before initializing the dash. My plan would be to embed the calls that build the dash inside my $scope.$on block. I started looking at how to run a run method synchronously before the controllers initialize, but maybe I don't need to.
Any obvious idea why the $scope.$on method does not seem to fire?
Thanks in advance
A different approach would be to place your $http functions in a service or factory and then resolve these in your controller.
The key here is the use of promise. Angular documentation describes this as
A service that helps you run functions asynchronously, and use their
return values (or exceptions) when they are done processing
First create a service:
app.factory('DataService', function($http) {
var getValues= function() {
var url = '/myurl/V3_portlet_settings?p_user_id='+ CUID;
return $http.jsonp(url) // returns a promise
};
return {
getValues: getValues
}
});
And then in your controller:
myApp.controller('MyController', function ($scope, DataService) {
DataService.getValues().then( // resolve the promise using .then()
function(data){
// successcallback
// you can now safely populate the data in you controller
console.log(data);
},
function(error){
// errorcallback
console.log(error);
})
});
I think it is a better approach to use data services to handle data operations such as $http requests.
The return of promises allows for chaining of (multiple) promises and better handling of async calls.
You might find John Papa's style guide useful, especially the section about 'Separate Data Calls' (Y060) and 'Return a Promise from Data Calls' (Y061)
i need to preload images after an $http service.
Now in my application i'm using promise/resolve method to preload data with service.
here my service:
angular.module('davelab.services', [])
.factory('srvProjects', ['$http', function ($http) {
var API = '/backend/api/';
var sdo = {
getProjects: function() {
var promise = $http({ method: 'GET', url: API + 'get_posts/' }).success(function(data, status, headers, config) {
return data;
});
return promise;
}
};
return sdo;
}]);
});
and here the route part:
$routeProvider.when('/projects', {
templateUrl: 'app/partials/list-projects.html',
controller: 'ProjectsCtrl',
resolve: {
projects: function(srvProjects) {
return srvProjects.getProjects();
}
}
});
it works well with data but for the images retrieved from api request it doesn't work.
How can I preload images in cache before show the view with this method?
i have to iterate through data into success callback or somewhere else?
thanks.
Take the URL's you're receiving from your original request, but instead of resolving the promise right away, create a new promise for each image. Preload the image using any method you prefer (but it needs a callback to resolve its promise) and resolve each promise after the image is loaded. You should end up with an array of image promises. and then return $q.all(imagePromises) from your resolve. Check out the docs for $q.
$q.all will return a promise that is only resolved when all promises passed into it are resolved.
I haven't tested it, but I think you might be able to just use $http on each image URL and it will cache it for you and give you a nice promise to pass into $q.all. So basically you'll have an array of $http calls.
You can use like this in your controller
$scope.preloader = true
then you can use angular promise of
.then(function(result){
//your code goes here
$scope.preloader = false;
}
Also you have $scope.preloader= false in you error block
If you have multiple Ajax calls at a same time parallely then use
$scope.preloader = 0;
on stating the request use
$scope.preloader++;
and when done use
$scope.preloader--;
You can have more clear answer here
Showing Spinner GIF during $http request in angular
I'm sure there is an easy way to do what I want, I just cant wrap my head around it. How can I get the http interceptor in angular to retry a request if it fails? I imagine I would have to build some sort of promise in the request right? Then in the response I would have to check whether the response was an error and if so, do the promise? How is that done? I have been trying to adapt the example here: http://docs.angularjs.org/api/ng.$http
The reason I am trying to use an interceptor is because I need to add the token and a few other things to the request url, as well as some things to handle xdr's.
Here's a $http interceptor that (immediately) replays timed out request(s) (i.e. response status 0). There were two non-obvious (to me!) elements to constructing this example:
How to call $http from within the interceptor - simply adding $http to the dependency list didn't work as angular complains of a circular dependency
How to reference the original request from the response object in order to retry it
This answer addresses both topics but is more involved so I include a simplified version below.
joinApp.factory('httpResponseErrorInterceptor',function($q, $injector) {
return {
'responseError': function(response) {
if (response.status === 0) {
// should retry
var $http = $injector.get('$http');
return $http(response.config);
}
// give up
return $q.reject(response);
}
};
});
joinApp.config(function($httpProvider) {
$httpProvider.interceptors.push('httpResponseErrorInterceptor');
});
In your actual implementation, you would likely want more sophisticated http response code processing, a limit to the number of times you retry, etc.
For the sake of completeness, here is a version of mygzi's answer with retry delay:
.factory('httpResponseErrorInterceptor', ['$injector', '$q', '$timeout', function($injector, $q, $timeout) {
return {
'responseError': function(response) {
if (response.status === 0) {
return $timeout(function() {
var $http = $injector.get('$http');
return $http(response.config);
}, 15000);
}
return $q.reject(response);
}
};
}])
.config(function($httpProvider) {
$httpProvider.interceptors.push('httpResponseErrorInterceptor');
});
$timeout returns a promise that is completed with what is returned from the function parameter, so we can conveniently just return the $http call wrapped in $timeout.
I've done this for an app I wrote before. To do the retry wrap the $http usage in a function (or preferably in a service so that you can reuse it easily). If the request fails, call the function again.
The trick is then to pass the promise object along with each request. If you create a new promise with each request then it won't match the one you returned to the original caller, so the original caller won't get his promise resolve once the request passes.
So it is something like this (note that the defer object is passed along in each retry):
app.service('HttpService', ['$q', function($q) {
this.makeRequest = _makeRequest;
function _makeRequest(url, data, deffered) {
// We want to keep the same promise for each request, so we don't loose track
if (deferred === undefined) {
deferred = $q.defer();
}
// Now make the request
$http({...}).success(...).error(
function(){
// If some condition
_makeRequest(url, data, deffered);
}
)
// Lastly return the promise
return deferred.promise;
}
}])