In my last post on Angular JS, we moved the data into an ASP.NET Web API controller and invoked the data using $http, a service in Angular JS that abstracts AJAX calls. But the code resided in the controller and that is not good. Responsibility of the controller should be to sit between data and view, but not to call services using AJAX. So, let’s move the AJAX calls to a separate component.
The AJAX calls made using $http service are executed asynchronously. They return promise objects, using which their status can be tracked. As we will be moving them into a custom service, the controller depending on them should be notified with the status of completion. The notification can be sent using the promise object returned by the service functions or, we can create our own deferred object using $q service.
I discussed about deferred and promise in jQuery earlier in a post. Basic usage of the pattern remains the same with $q as well, except a small difference syntax. Following snippet is shows the way of creating a deferred object, calling success and failure functions using $q:
While calling the above function, a callback to be called on success and a callback to be handled on failure should be attached. It might look as follows:
The functions responsible for making AJAX calls should follow this pattern as the functions in $http are executed asynchronously. To notify the dependent logic about state of execution of the functionality, we have to use the promise pattern.
As we will be enclosing this functionality in a custom service which requires $http for AJAX and $q for promise, these dependencies will be injected at the runtime when the module is loaded. Following snippet demonstrates the implementation of a function to retrieve items in shopping cart calling Web API service:
Following is the controller consuming the above custom service:
As you see parameters of the controller, the second parameter shoppingData will be injected by the dependency injector during runtime. The function refreshItems follows the same convention as the snippet we discussed earlier. It does the following:
The controller is free from any logic other than updating data that is displayed on the UI.
Functions to add a new item and remove an existing item also follow similar pattern. You can find the complete code in the following github repo: AngularShoppingCart
Happy coding!
The AJAX calls made using $http service are executed asynchronously. They return promise objects, using which their status can be tracked. As we will be moving them into a custom service, the controller depending on them should be notified with the status of completion. The notification can be sent using the promise object returned by the service functions or, we can create our own deferred object using $q service.
I discussed about deferred and promise in jQuery earlier in a post. Basic usage of the pattern remains the same with $q as well, except a small difference syntax. Following snippet is shows the way of creating a deferred object, calling success and failure functions using $q:
function myAsyncFunc(){ //Creating a deferred object var deferred = $q.defer(); operation().success(function(){ //Calling resolve of deferred object to execute the success callback deferred.resolve(data); }).error(function(){ //Calling reject of deferred object to execute failure callback deferred.reject(); }); //Returning the corresponding promise object return deferred.promise; }
While calling the above function, a callback to be called on success and a callback to be handled on failure should be attached. It might look as follows:
myAsyncFunction().then(function(data){ //Update UI using data or use the data to call another service }, function(){ //Display an error message });
The functions responsible for making AJAX calls should follow this pattern as the functions in $http are executed asynchronously. To notify the dependent logic about state of execution of the functionality, we have to use the promise pattern.
As we will be enclosing this functionality in a custom service which requires $http for AJAX and $q for promise, these dependencies will be injected at the runtime when the module is loaded. Following snippet demonstrates the implementation of a function to retrieve items in shopping cart calling Web API service:
angular.module('shopping', []). factory('shoppingData',function($http, $q){ return{ apiPath:'/api/shoppingCart/', getAllItems: function(){ //Creating a deferred object var deferred = $q.defer(); //Calling Web API to fetch shopping cart items $http.get(this.apiPath).success(function(data){ //Passing data to deferred's resolve function on successful completion deferred.resolve(data); }).error(function(){ //Sending a friendly error message in case of failure deferred.reject("An error occured while fetching items"); }); //Returning the promise object return deferred.promise; } } }
Following is the controller consuming the above custom service:
function ShoppingCartCtrl($scope, shoppingData) { $scope.items = []; function refreshItems(){ shoppingData.getAllItems().then(function(data){ $scope.items = data; }, function(errorMessage){ $scope.error=errorMessage; }); }; refreshItems(); };
As you see parameters of the controller, the second parameter shoppingData will be injected by the dependency injector during runtime. The function refreshItems follows the same convention as the snippet we discussed earlier. It does the following:
- On successful completion of getAllItems() function, it assigns data to a property of $scope object which will be used to bind data
- If the execution fails, it assigns the error message to another property of $scope object using which we can display the error message on the screen
The controller is free from any logic other than updating data that is displayed on the UI.
Functions to add a new item and remove an existing item also follow similar pattern. You can find the complete code in the following github repo: AngularShoppingCart
Happy coding!
awsome post...really,found such a good post after a long time...gr8 work..
ReplyDeleteThanks Prashant!
DeleteGood explanation of $q and defer, thank you.
ReplyDeleteWelcome!
Deletevery useful! thanks! :)
ReplyDeleteIn the 3rd example you are missing a curly brace that should be on line 23.
ReplyDeleteAh! Thanks for pointing. I fixed it.
DeleteHello Ravi:
ReplyDeleteI came across your posts on angular deferred and promises. I have been struggling to get this done and I am very new to angular. I followed what was posted on your blog but that didn't help. I seem to be doing exactly what you have specified but it won't work. I am using service as opposed to factory in your example but for the sake of trial I even tried using a factory but that won't work either.
And here is the question I posted on stackoverflow. Can you please help by pointing what I am doing wrong?
http://stackoverflow.com/questions/18755201/angularjs-http-get-to-get-json-not-working-in-the-service-layer
Thanks for this tutorial ;)
ReplyDeleteJeez, spent hours and hours trying to figure out how to use promises to do callbacks from service to controller and found heaps upon heaps of poorly explained crap until ran into this. You sir are awesome ! Here, have a virtual hug *bear hug* :D
ReplyDeleteThanks Khalid. Hugs!!!
DeleteHi, good post, but i made all http methods is generic in factory function and i pass the url from controller if i calling same method with different url its not working and in controller two method are getting same response.
ReplyDeletelike:--
shoppingData.getAllItems('/api/shoppingCart/').then(function(data){
$scope.items = data;
},
shoppingData.getAllItems('/api/somethingElse/').then(function(data){
$scope.someThing = data;
},
Veeru,
DeleteCan you post your factory and a dummy controller in a fiddle (jsfiddle.com) or plunk (plnkr.co) and share the link? It will be even better of you can ask the question on stackoverflow.com, as it will be public and will help a number of other developers as well.
Hi ravi good post
ReplyDeleteif you call service xhr inside a service authentification and you want to access it
in your controller how would you approach it?
Gabster,
DeleteThis gist might help you: https://gist.github.com/robfarmergt/7510013#file-angularlogin. Let me know if you need more help.
Finally a tutorial with a realistic scenario. Thanks!
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteDoesn't $http.get return a promise anyway, in which case you could just return that and do away with $q altogether? Or have I missed something fundamental? :)
ReplyDeleteJames, Sorry for the slow response.
DeleteI agree that the example in this post doesn't explain clearly why to go for the $q promise. You can use custom promise to have better control. For example, if you want to send the error received from the REST API to an endpoint for logging on the server, you can do it in the service; as controller id not supposed to do it. This is just one example, there are a number of such scenarios where custom promise is useful.
Yep, that is my question also! :) It shows how to use $q lib though.
ReplyDeleteYou just save me the day, I didn't know anything about the deferred object. I was looking all day for this solution. Thx man!
ReplyDeleteHello!
ReplyDeletei'm having this error:
"Failed to load resource: net::ERR_CONNECTION_REFUSED"
and my path is like this:
apiPath: 'http://localhost/appCardapioWs/api/app_bebidaApi/'
when I try this link on my browser, it works..
What may I'm doing wrong?
Me too. Have you find a solution ?
ReplyDeleteIt is a very good post. I found it very helpful. Thanks :)
ReplyDelete