Angular JS Promises: From Service to Template

In this blog post, we will learn how to request an HTTP GET call from an AngularJS Service and display the data in the template while avoiding the common pitfall of loading the template before the asynchronous call has been received.

In order to do this we’ll be diving into Angular Promises, specifically Kris Kowal's Q. Please know that this blog post assumes you are familiar with the concepts of $scope, templates and dependency injection (DI).

Let's begin.

What's a service?

In AngularJS, a service is a function or an object that shares data and/or behavior across your application. Wherever we want to use a service, we just have to specify its name and AngularJS injects these objects.

So, a service is a stateless object that contains some useful functions. These functions can be called from anywhere: Controllers, Directive, Filters etc. We can divide our application in logical units. The business logic or logic to call HTTP url to fetch data from server can be put within a service object.

How to create a service

There are various ways to create a service. I like to write them using an array notation for various reasons; chief amongst them is to avoid the Error: $injector:unpr and any subsequent errors I might encounter should I choose to minify my code. Also, it's just good practice to define the services you'll be using.

Here's our custom service:

angular.module('myApp')
  .service('myService', ['$http', '$q', function($http, $q) {
 
    var myMethods = {
        // Code here
     }
 
     return myMethods;
  }]);

Promises and other asynchronous functions

Now let's dive into promises. This blog post assumes you have some prior knowledge of promises and some understanding of asynchronous calls. Nevertheless, let's review to ensure we have the same understanding.

A promise simply represents the eventual result of an asynchronous operation.

So let's state our promise within our custom service:

angular.module('myApp')
  .service('myService', ['$http', '$q', function($http, $q){
    var myMethods = {
 
      getPromise: function() {
        var promise = $http.get('/someURL');
 
    }
  }
 
   return myMethods;
 
  }]);

Now, let's add the logic to our variable "promise" to let it know what to do once it receives the $http.get call. We are going to handle the promise using Kris Kowal's Q implementation.

So first, let's define $q.

We are going to instantiate a $q.defer() object. This is basically an "IOU" that we will be passing to the function that needs our promise until we have resolved the promise itself.

For this, I like this little JavaScript trick I learned from one of our developers here, Slavko Pesic to avoid creating multiple $q.defer() objects. It goes like this:

// Global variable
var x;
 
// Within our function
var x = x || $q.defer();

So now when you use x as a variable it will first go see if that variable exists, if it does, it will use x, if not it will create an $q.defer() object and assign it to x. From here on out, we can use that one variable "x" instead of instantiating multiple $q.defer() objects (e.g. multiple IOUs).

Taking this into consideration, this is how we will instantiate our $q.defer() object and how our service is looking so far.

angular.module('myApp')
  .service('myService', ['$http', '$q', function($http, $q){
    var deferObject,
 
    myMethods   = {
 
      getPromise: function() {
        var promise        =  $http.get('/someURL'),
              deferObject  =  deferObject || $q.defer();
 
         }
      };
 
      return myMethods;
 
  }]);

Here is where our then method comes in. This then method takes in two parameters. These two parameters are a function to run if the promise is successful and a function to run if the promise has an error.

promise.then(onSuccess, onFailure);

Here is how our service is looking after adding the then method:

angular.module('myApp')
  .service('myService', ['$http', '$q', function($http, $q){
     var deferObject,
     myMethods = {
 
       getPromise: function() {
         var promise        =  $http.get('/someURL'),
               deferObject  =  deferObject || $q.defer();
 
               promise.then(
               // OnSuccess function
               function(answer){
                 // This code will only run if we have a successful promise.
               },
               // OnFailure function
               function(reason){
                 // This code will only run if we have a failed promise.
               });
            }
        };
 
        return myMethods;
 
       }]);

All we need now is to resolve our $q.defer() object. We do this with the methods provided by the Deferred API docs within the AngularJS documentation.

Deferred API Methods

  • resolve(value) – resolves the derived promise with the value. If the value is a rejection constructed via $q.reject, the promise will be rejected instead.
  • reject(reason) – rejects the derived promise with the reason. This is equivalent to resolving it with a rejection constructed via $q.reject.
  • notify(value) - provides updates on the status of the promise's execution. This may be called multiple times before the promise is either resolved or rejected.

We will be using resolve for when our promise is successful and reject for when it is unsuccessful. Here is how our service looks:

  angular.module('myApp')
    .service('myService', ['$http', '$q', function($http, $q){
      var deferObject,
      myMethods = {
 
        getPromise: function() {
          var promise       =  $http.get('/someURL'),
                deferObject =  deferObject || $q.defer();
 
                promise.then(
                  // OnSuccess function
                  function(answer){
                    // This code will only run if we have a successful promise.
                    deferObject.resolve(answer);
                  },
                  // OnFailure function
                  function(reason){
                    // This code will only run if we have a failed promise.
                    deferObject.reject(reason);
                  });
 
           return deferObject.promise;
          }
       };
 
       return myMethods;
 
    }]);

That is all we need to do in our angular service.

Now, we must ensure our controller is aware that the method getPromise is a promise. So in our controller we must also use a .then() method on the variable that calls our promise. This is how our controller looks:

  angular.module('myApp')
    .controller('myController',['$scope', 'myService', function($scope, myService){
       var $scope.success   = false,
             $scope.error       = false;
 
      var askForPromise = myService.getPromise();
 
      askForPromise.then(
        // OnSuccess function
        function(answer) {
          $scope.somethingRight = answer;
          $scope.success = true;
        },
        // OnFailure function
        function(reason) {
          $scope.somethingWrong = reason;
          $scope.error = true;
        }
      )
    }]);

Notice how we can assign the data retrieved from the promise to the $scope object and display different data depending on whether or not our promise was successful.

Now in our template we simply data bind our variables.

  <div ng-controller="myController">
 
      <div ng-show="success">{{ somethingRight }}</div>
      <div ng-show="error">{{ somethingWrong }}</div>
 
  </div>

That's it!

Thank you for this post. I could not figure out why my promise inside a service was only working correctly the first time called. Creating a single $q.defer object fixed the bug.

Add new comment

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>, <cpp>, <java>, <php>. The supported tag styles are: <foo>, [foo].
  • Web page addresses and email addresses turn into links automatically.
  • Lines and paragraphs break automatically.
By submitting this form, you accept the Mollom privacy policy.

About the Author