Lesson 8: Promises

In this Lesson, we will cover the following recipes:

  • Understanding and implementing a basic promise
  • Chaining promises and promise handlers
  • Implementing promise notifications
  • Implementing promise barriers with $q.all()
  • Creating promise wrappers with $q.when()
  • Using promises with $http
  • Using promises with $resource
  • Using promises with Restangular
  • Incorporating promises into native route resolves
  • Implementing nested ui-router resolves

Introduction

AngularJS promises are an odd and fascinating component of the framework. They are integral to a large number of core components, and yet many references only mention them in passing. They offer an extremely robust and advanced mechanism of application control, and as application complexity begins to scale up, you as an AngularJS developer will find that promises are nearly impossible to ignore. This, however, is a good thing; promises are extraordinarily powerful, and they will make your life much simpler once they are fully understood.

Note

AngularJS promises will soon be subjected to a good deal of modification with the upcoming ES6 promise implementation. Currently, they are a bit of a hybrid implementation, with the CommonJS promise proposal as the primary influence. As ES6 becomes more widely disseminated, the AngularJS promise implementation will begin to converge with that of native ES6 promises.

Understanding and implementing a basic promise

Promises are absolutely essential to many of the core aspects of AngularJS. When learning about promises for the first time, the formal terms can be an impediment to their complete understanding as their literal definitions convey very little about how the actual promise components act.

How to do it…

A promise implementation in one of its simplest forms is as follows:

// create deferred object through $q api
var deferred = $q.defer();

// deferred objects are created with a promise attached
var promise = deferred.promise;

// define handlers to execute once promise state becomes definite
promise.then(function success(data) {
  // deferred.resolve() handler
  // in this implementation, data === 'resolved'
}, function error(data) {
  // deferred.reject() handler
  // in this implementation, data === 'rejected'
});

// this function can be called anywhere to resolve the promise
function asyncResolve() {
  deferred.resolve('resolved');
};

// this function can be called anywhere to reject the promise
function asyncReject() {
  deferred.reject('rejected');
};

To a person seeing promises for the first time, what makes them difficult to comprehend is quite plain: much of what is going on here is not intuitive.

How it works…

The promise ecosystem can be more readily decoded by gaining a better understanding of the nomenclature behind it, and what problems it intends to solve.

Promises are by no means a new concept in AngularJS, or even in JavaScript; part of the inspiration for $q was taken from Kris Kowal's Q library, and for a long time, jQuery has had key promise concepts incorporated into many of its features.

Promises in JavaScript confer to the developer the ability to write asynchronous code in parallel with synchronous code more easily. In JavaScript, this was formerly solved with nested callbacks, colloquially referred to as callback hell. A single callback-oriented function might be written as follows:

// a prototypical asynchronous callback function
function asyncFunction(data, successCallback, errorCallback) {
  // this will perform some operation that may succeed,
  // may fail, or may not return at all, any of which
  // occurs in an unknown amount of time

  // this pseudo-response contains a success boolean,
  // and the returned data if successful
  var response = asyncOperation(data);

  if (response.success === true) {
    successCallback(response.data);
  } else {
    errorCallback();
  }
};

If your application does not demand any semblance of in-order or collective completion, then the following will suffice:

function successCallback(data) {
  // asyncFunction succeeded, handle data appropriately
};
function errorCallback() {
  // asyncFunction failed, handle appropriately
};

asyncFunction(data1, successCallback, errorCallback);
asyncFunction(data2, successCallback, errorCallback);
asyncFunction(data3, successCallback, errorCallback);

This is almost never the case though, since your application will often demand either that the data should be acquired in a sequence or that an operation that requires multiple asynchronously-acquired pieces of data should only be executed once all the pieces have been successfully acquired. In this case, without access to promises, the callback hell emerges, as follows:

asyncFunction(data1, function(foo) {
  asyncFunction(data2, function(bar) {
    asyncFunction(data3, function(baz){
      // foo, bar, baz can now all be used together
      combinatoricFunction(foo, bar, baz);
    }, errorCallback);
  }, errorCallback);
}, errorCallback);

This so-called callback hell is really just attempting to serialize three asynchronous calls, but the parametric topology of these asynchronous functions forces the developer to subject their application to this ugliness. Promises to the rescue!

Note

From this point forward in this recipe, promises will be discussed pertaining to how they are implemented within AngularJS, rather than the conceptual definition of a promise API. There is a substantial overlap between the two, but for your benefit, the discussion in this recipe will lean towards the side of implementation rather than theory.

Basic components and behavior of a promise

The AngularJS promise architecture exposed by the $q service decomposes into a dichotomy: deferreds and promises.

Deferreds

A deferred is the interface through which the application will set and alter the state of the promise.

An AngularJS deferred object has exactly one promise attached to it by default, which is accessible through the promise property, as follows:

var deferred = $q.defer()
  , promise = deferred.promise;

In the same way that a single promise can have multiple handlers bound to a single state, a single deferred can be resolved or rejected in multiple places in the application, as shown here:

var deferred = $q.defer()
  , promise = deferred.promise; 

// the following are pseudo-methods, each of which can be called 
// independently and asynchronously, or not at all
function canHappenFirst() { deferred.resolve(); }
function mayHappenFirst() { deferred.resolve(); }
function mightHappenFirst() { deferred.reject(); }

Once a deferred's state is set to resolved or rejected anywhere in the application, attempts to reject or resolve that deferred further will be silently ignored. A promise state transition occurs only once, and it cannot be altered or reversed. Refer to the following code:

var deferred = $q.defer()
  , promise = deferred.promise; 

// define handlers on the promise to gain visibility 
// into their execution
promise.then(function resolved() {
  $log.log('success');
}, function rejected() {
  $log.log('rejected');
});

// verify initial state
$log.log(promise.$$state.status); // 0

// resolve the promise
deferred.resolve();
// >> "resolved"

$log.log(promise.$$state.status); // 1
// output and state check verify state transition

// attempt to reject the already resolved promise
deferred.reject();

$log.log(promise.$$state.status); // 1
// output and state check verify no state transition
Promises

A promise represents an unknown state that could transition into a known state at some point in the future.

A promise can only exist in one of three states. AngularJS represents these states within the promises with an integer status:

  • 0: This is the pending state that represents an unfulfilled promise waiting for evaluation. This is the initial state. An example is as follows:
    var deferred = $q.defer()
      , promise = deferred.promise;
    
    $log.log(promise.$$state.status); // 0
  • 1: This is the resolved state that represents a successful and fulfilled promise. A transition to this state cannot be altered or reversed. An example is as follows:
    var deferred = $q.defer()
      , promise = deferred.promise;
    
    $log.log(promise.$$state.status); // 0
    
    deferred.resolve('resolved');
    
    $log.log(promise.$$state.status); // 1
    $log.log(promise.$$state.value);  // "resolved"
  • 2: This is the rejected state that represents an unsuccessful and rejected promise caused by an error. A transition to this state cannot be altered or reversed. An example is as follows:
    var deferred = $q.defer()
      , promise = deferred.promise;
    
    $log.log(promise.$$state.status); // 0
    
    deferred.reject('rejected');
    
    $log.log(promise.$$state.status); // 2
    $log.log(promise.$$state.value);  // "rejected"

States do not necessarily have a data value associated with them—they only confer to the promise a defined state of evaluation. Take a look at the following code:

var deferred = $q.defer()
  , promise = deferred.promise;

promise.then(successHandler, failureHandler);

// state can be defined with any of the following:
// deferred.resolve();
// deferred.reject();
// deferred.resolve(myData);
// deferred.reject(myData);

An evaluated promise (resolved or rejected) is associated with a handler for each of the states. This handler is invoked upon the promise's transition into that respective state. These handlers can access data returned by the resolution or rejection, as shown here:

var deferred = $q.defer()
  , promise = deferred.promise;

// $log.info is the resolve handler,
// $log.error is the reject handler 
promise.then($log.info, $log.error);

deferred.resolve(123);  
// (info) 123

// reset to demonstrate reject()
deferred = $q.defer();
promise = deferred.promise;

promise.then($log.log, $log.error);

deferred.reject(123);  
// (error) 123

Unlike callbacks, handlers can be defined at any point in the promise life cycle, including after the promise state has been defined, as shown here:

var deferred = $q.defer()
  , promise = deferred.promise;

// immediately resolve the promise
deferred.resolve(123);

// subsequently define a handler, will be immediately
// invoked since promise is already resolved 
promise.then($log.log); 
// 123

In the same way that a single deferred can be resolved or rejected in multiple places in the application, a single promise can have multiple handlers bound to a single state. For example, a single promise with multiple resolved handlers attached to it will invoke all of the handlers if the resolved state is reached; the same is true for rejected handlers as well. This is shown here:

var deferred = $q.defer()
  , promise = deferred.promise
  , cb = function() { $log.log('called'); };

promise.then(cb);
promise.then(cb);

deferred.resolve();
// called
// called

Note

Variables, object properties, or methods preceded with $$ denote that they are private, and while they are very handy for inspection and debugging purposes, they shouldn't be touched in production applications without good reason.

See also

  • The Chaining promises and promise handlers recipe provides the details of combinatorial strategies involving promises in order to create an advanced application flow
  • The Implementing promise notifications recipe demonstrates how to use notifications for intermediate communication when a promise takes a long time to get resolved
  • The Implementing promise barriers with $q.all() recipe shows how to combine a group of promises into a single, all-or-nothing promise
  • The Creating promise wrappers with $q.when() recipe shows how to normalize JavaScript objects into promises

Chaining promises and promise handlers

Much of the purpose of promises is to allow the developer to serialize and reason about independent asynchronous actions. This can be accomplished by utilizing promise chaining in AngularJS.

Getting ready

Assume that all the examples in this recipe have been set up in the following manner:

var deferred = $q.defer()
    , promise = deferred.promise;

Also, assume that $q and other built-in AngularJS services have already been injected into the current lexical scope.

How to do it…

The promise handler definition method then() returns another promise, which can further have handlers defined upon it in a chain handler, as shown here:

var successHandler = function() { $log.log('called'); };

promise
  .then(successHandler)
  .then(successHandler)
  .then(successHandler);

deferred.resolve();
// called
// called
// called

Data handoff for chained handlers

Chained handlers can pass data to their subsequent handlers, as follows:

var successHandler = function(val) { 
  $log.log(val); 
  return val+1;
};

promise
  .then(successHandler)
  .then(successHandler)
  .then(successHandler);

deferred.resolve(0);
// 0
// 1
// 2

Rejecting a chained handler

Returning normally from a promise handler will, by default, signal child promise states to become resolved. If you want to signal child promises to get rejected, you can do so by returning $q.reject(). This can be done as follows:

promise
.then(function () {
  // initial promise resolved handler instructs handlers
  // child promise(s) to be rejected
  return $q.reject(123);
})
.then(
  // child promise resolved handler
  function(data) {
    $log.log("resolved", data);
  },
  // child promise rejected handler
  function(data) {
    $log.log("rejected", data);
  }
);

deferred.resolve();
// "rejected", 123

How it works…

A promise reaching a final state will trigger child promises to follow it in turn. This simple but powerful concept allows you to build broad and fault-tolerant promise structures that elegantly mesh collections of dependent asynchronous actions.

There's more…

The topology of AngularJS promises lends itself to some interesting utilization patterns, as follows.

Promise handler trees

Promise handlers will be executed in the order that the promises are defined. If a promise has multiple handlers attached to a single state, then that state will execute all its handlers before resolving the following chained promise. This is shown here:

var incr = function(val) {
  $log.log(val);
  return val+1;
}

// define the top level promise handler
promise.then(incr);
// append another handler for the first promise, and collect
// the returned promise in secondPromise
var secondPromise = promise.then(incr);
// append another handler for the second promise, and collect
// the returned promise in thirdPromise
var thirdPromise = secondPromise.then(incr);

// at this point, deferred.resolve() will:
// resolve promise; promise's handlers executes
// resolve secondPromise; secondPromises's handler executes
// resolve thirdPromise; no handlers defined yet

// additional promise handler definition order is
// unimportant; they will be resolved as the promises
// sequentially have their states defined
secondPromise.then(incr);
promise.then(incr);
thirdPromise.then(incr);

// the setup currently defined is as follows:
// promise -> secondPromise -> thirdPromise
// incr()     incr()           incr()
// incr()     incr()
// incr()

deferred.resolve(0);
// 0
// 0
// 0
// 1
// 1
// 2

Note

JSFiddle: http://jsfiddle.net/msfrisbie/4msybmc9/

Since the return value of a handler decides whether or not the promise state is resolved or rejected, any of the handlers associated with a promise are able to set the state—which, as you may recall, can only be set once. The defining of the parent promise state will trigger the child promise handlers to execute.

It should now be apparent how trees of the promise functionality can be derived from the combinations of promise chaining and handler chaining. When used properly, they can yield extremely elegant solutions to difficult and ugly asynchronous action serialization.

The catch() method

The catch() method is a shorthand for promise.then(null, errorCallback). Using it can lead to slightly cleaner promise definitions, but it is no more than syntactical sugar. It can be used as follows:

promise
.then(function () {
  return $q.reject();
})
.catch(function(data) {
  $log.log("rejected");
});

deferred.resolve();
// "rejected"

The finally() method

The finally() method will execute irrespective of whether the promise was rejected or resolved. It is convenient for applications that need to perform some sort of cleanup, independent of what the final state of the promise becomes. It can be used as follows:

var deferred1 = $q.defer();
  , promise1 = deferred1.promise
  , deferred2 = $q.defer()
  , promise2 = deferred2.promise
  , cb = $log.log("called");

promise1.finally(cb);
promise2.finally(cb);

deferred1.resolve();
// "called"
deferred2.reject();
// "called"

See also

  • The Understanding and implementing a basic promise recipe goes into more detail about how AngularJS promises work
  • The Implementing promise notifications recipe demonstrates how to use notifications for intermediate communication when a promise takes a long time to get resolved
  • The Implementing promise barriers with $q.all() recipe shows how to combine a group of promises into a single, all-or-nothing promise
  • The Creating promise wrappers with $q.when() recipe shows how to normalize JavaScript objects into promises

Implementing promise notifications

AngularJS also offers the ability to provide notifications about promises before a final state has been reached. This is especially useful when promises have long latencies and updates on their progress is desirable, such as progress bars.

How to do it…

The promise.then() method accepts a third argument, a notification handler, which can be accessed through the deferred an unlimited number of times until the promise state has been resolved. This is shown here:

promise
.then(
  // resolved handler
  function() {
    $log.log('success');
  },
  // empty rejected handler
  null,
  // notification handler
  $log.log
);

function resolveWithProgressNotifications() {
  for (var i=0; i<=100; i+=20) {
    // pass the data to the notification handler
    deferred.notify(i);
    if (i>=100) { deferred.resolve() };
  };
}

resolveWithProgressNotifications();
// 0
// 20
// 40
// 60
// 80
// 100
// "success"

How it works…

The notification handler allows the notifications to be enqueued upon the promise, and they are sequentially executed at the conclusion of the $digest cycle. Another example is as follows:

promise
.then(
  function() {
    $log.log('success');
  },
  null,
  $log.log
);

function asyncNotification() {
  deferred.notify('Hello, ');
  $log.log('world!');
  deferred.resolve();
};

// this function is invoked by some non-AngularJS entity
asyncNotification();
// world!
// Hello, 
// success

The order of the console log statements might surprise you. Since the notifications often arrive from an event that is not bound to the AngularJS $digest cycle, a call to $scope.$apply() will push through the execution of the notification handler(s) immediately. This is shown here:

promise
.then(
  function() {
    $log.log('success');
  },
  null,
  $log.log
);

function newAsyncNotification() {
  deferred.notify('Hello, ');
  $scope.$apply();
  $log.log('world!');
  deferred.resolve();
};

// this function is invoked by some non-AngularJS entity
newAsyncNotification();
// Hello,
// world!
// success

There's more…

The notification handler cannot transit the promise into a final state with its return value, although it can use the deferred object to cause a state transition, as demonstrated earlier in this recipe.

Notifications will not be executed after the promise has transitioned to a final state, as shown here:

// resolve or reject handlers not needed in this example
promise.then(null, null, $log.log);

deferred.notify('Hello, ');
deferred.resolve();
deferred.notify('world!');

// Hello, 

Your Coding Challenge

We've seen how to implement a promise notification before the application reaches the final state. Let's display the progress of the action to be executed (progress bar) and finally a success message!

To do this, you can follow along with how we implemented the hello world! example. The completion rate is what needs to be taken care of.

Once this is done, we are all set to move on to our next recipe!

Implementing promise barriers with $q.all()

You might find that your application requires the use of promises in an all-or-nothing type of situation. That is, it will need to collectively evaluate a group of promises, and that collection will be resolved as a single promise if and only if all of the contained promises are resolved; if any one of them is rejected, the aggregate promise will be rejected.

How to do it…

The $q.all() method accepts an enumerable collection of promises, either an array of promise objects or an object with a number of promise properties, and will attempt to resolve all of them as a single aggregate promise. The parameter of the aggregate resolved handler will be an array or object that matches the resolved values of the contained promises. This is shown here:

var deferred1 = $q.defer()
  , promise1 = deferred1.promise
  , deferred2 = $q.defer()
  , promise2 = deferred2.promise;

$q.all([promise1, promise2]).then($log.log);

deferred1.resolve(456);
deferred2.resolve(123);
// [456, 123]

If any of the promises in the collection are rejected, the aggregate promise will be rejected. The parameter of the aggregate rejected handler will be the returned value of the rejected promise. This is shown here:

var deferred1 = $q.defer()
  , promise1 = deferred1.promise
  , deferred2 = $q.defer()
  , promise2 = deferred2.promise;

$q.all([promise1, promise2]).then($log.log, $log.error);

// resolve a collection promise, no handler execution
deferred1.resolve(456);

// reject a collection promise, rejection handler executes
deferred2.reject(123);
// (error) 123

How it works…

As demonstrated, the aggregate promise will reach a final state only when all of the enclosed promises are resolved, or when a single enclosed promise is rejected. Using this type of promise is useful when the promises in a collection do not need to reason about one another, but their collective completion is the only metric of success for the group.

In the case of a contained rejection, the aggregate promise will not wait for the remaining promises to get completed, but those promises will not be prevented from reaching their final state. Only the first promise to be rejected will be able to pass the rejection data to the aggregate promise rejection handler.

There's more…

The $q.all() method is in many ways extremely similar to an operating-system-level process synchronization barrier. A process barrier is a common point in a thread instruction execution, which a collection of processes will reach independently and at different times, and none can proceed until all have reached this point. In the same way, $q.all() will not proceed unless either all of the contained promises have been resolved (reached the barrier) or a single contained rejection has prevented that state from ever being achieved, in which case the failover handler logic will take over.

Since $q.all() allows the recombination of promises, this also allows your application's promise chains to become a directed acyclic graph (DAG). The following diagram is an example of a promise progression graph that has diverged and later converged:

There's more…

This level of complexity is uncommon, but it is available for use should your application require it.

See also

  • The Understanding and implementing a basic promise recipe goes into more detail about how AngularJS promises work
  • The Chaining promises and promise handlers recipe provides the details of combinatorial strategies that involve promises to create an advanced application flow
  • The Implementing promise barriers with $q.all() recipe shows how to combine a group of promises into a single, all-or-nothing promise
  • The Creating promise wrappers with $q.when() recipe shows how to normalize JavaScript objects into promises

Creating promise wrappers with $q.when()

AngularJS includes the $q.when() method that allows you to normalize JavaScript objects into promise objects.

How to do it…

The $q.when() method accepts promise and non-promise objects, as follows:

var deferred = $q.defer()
  , promise = deferred.promise;

$q.when(123);
$q.when(promise);
// both create new promise objects

If $q.when() is passed a non-promise object, it is effectively the same as creating an immediately resolved promise object, as shown here:

var newPromise = $q.when(123);

// promise will wait for a $digest cycle to update $$state.status,
// this forces it to update for inspection
$scope.$apply();

// inspecting the status reveals it has already resolved
$log.log(newPromise.$$state.status);
// 1

// since it is resolved, the handler will execute immediately
newPromise.then($log.log);
// 123

How it works…

The $q.when() method wraps whatever is passed to it with a new promise. If it is passed a promise, the new promise will retain the state of that promise. Otherwise, if it is passed a non-promise value, the new promise created will get resolved and pass that value to the resolved handler.

Note

Keep in mind that the $q.reject() method returns a rejected promise, so $q.when($q.reject()) is simply wrapping an already rejected promise.

There's more…

Since $q.when() will return an identical promise when passed a promise, this method is effectively idempotent. However, the promise argument and the returned promise are different promise objects, as shown here:

$log.log($q.when(promise)===promise);
// false

See also

  • The Understanding and implementing a basic promise recipe goes into more detail about how AngularJS promises work
  • The Chaining promises and promise handlers recipe provides the details of combinatorial strategies that involve promises to create an advanced application flow
  • The Implementing promise notifications recipe demonstrates how to use notifications for intermediate communication when a promise takes a long time to get resolved
  • The Implementing promise barriers with $q.all() recipe shows how to combine a group of promises into a single, all-or-nothing promise

Using promises with $http

HTTP requests are the quintessential variable latency operations that demand a promise construct. Since it would appear that developers are stuck with the uncertainty stemming from TCP/IP for the foreseeable future, it behooves you to architect your applications to account for this.

How to do it…

The $http service methods return an AngularJS promise with some extra methods, success() and error(). These extra methods will return the same promise returned by the $http service, as opposed to .then(), which returns a new promise. This allows you to chain the methods as $http().success().then() and have the .success() and .then() promises attempt to resolve simultaneously.

The following two implementations are more or less identical, as everything is being chained upon the $http promise:

// Implementation #1
// $http.get() returns a promise
$http.get('/myUrl')
// .success() is an alias for the resolved handler
.success(function(data, status, headers, config, statusText) {
  // resolved handler
})
// .error() is an alias for the rejected handler
.error(function(data, status, headers, config, statusText) {
  // rejected handler
});

// Implementation #2
$http.get('/myUrl')
.then(
  // resolved handler
  function(response) {
    // response object has the properties
    // data, status, headers, config, statusText
  },
  // rejected handler
  function(response) {
    // response object has the properties
       // data, status, headers, config, statusText
  }
); 

However, the following two implementations are not identical:

// Implementation #3
// $http.get() returns a promise
$http.get('/myUrl')
// .success() is an alias for the resolved handler
.success(function(data, status, headers, config, statusText) {
  // resolved handler
})
// .error() is an alias for the rejected handler
.error(function(data, status, headers, config, statusText) {
  // rejected handler
})
.then( ... );

// Implementation #4
$http.get('/myUrl')
.then(
  // resolved handler
  function(response) {
    // response object has the properties
    // data, status, headers, config, statusText
  },
  // rejected handler
  function(response) {
    // response object has the properties
       // data, status, headers, config, statusText
  }
)
.then( ... );

The differences are explained in the following example:

// these are split into variables to be able to inspect
// the returned promises
var a = $http.get('/')
  , b = a.success(function() {})
  , c = b.error(function() {})
  , d = c.then(function() {});

$log.log(a===b, a===c, a===d, b===c, b===d, c===d);
//       true   true   false  true   false  false

var e = $http.get('/')
  , f = e.then(function() {})
  , g = e.then(function() {});

$log.log(e===f, e===g, f===g);
//       false  false  false

Note

JSFiddle: http://jsfiddle.net/msfrisbie/sh60bhc8/

For the sake of this example, the $http.get() requests are only accessing routes from the same domain that served the page. Keep in mind that using a foreign origin URL in the context of this example will bring about Cross-origin resource sharing (CORS) errors unless you properly modify the request headers to allow CORS requests.

How it works…

The success/error dichotomy for an HTTP request is decided by the response status code, as follows:

  • Any code between 200 and 299 will register as a successful request and the resolved handler will be executed
  • Any code between 300 and 399 will indicate a redirect, and XMLHttpRequest will follow the redirect to acquire a concrete status code
  • Any code between 400 and 599 will register as an error and the rejected handler will be executed

See also

  • The Using promises with $resource recipe discusses how ngRoute can be used as a promise-centric resource manager
  • The Using promises with Restangular recipe demonstrates how the popular third-party resource manager is extensively integrated with AngularJS promise conventions

Using promises with $resource

As part of the ngResource module, $resource provides a service to manage connections with RESTful resources. As far as vanilla AngularJS goes, this is in some ways the closest you'll get to a formal data object model infrastructure. The $resource tool is highly extensible and is an excellent standalone tool upon which to build applications if third-party libraries like Restangular aren't your cup of tea.

As the API-focused wrapper for $http, $resource also provides an interface for using promises in conjunction with the HTTP requests that it generates.

How to do it…

Although it wraps $http, $resource actually does not use promises in its default implementation. The $promise property can be used to access the promise object of the HTTP request, as follows:

// creates the resource object, which exposes get(), post(), etc.
var Widget = $resource('/widgets/:widgetId', {widgetId: '@id'});

// resource object must be coaxed into returning its promise
// this can be done with the $promise property
Widget.get({id: 8})
.$promise
.then(function(widget) {
  // widget is the returned object with id=8
});

How it works…

A $resource object accepts success and error function callbacks as its second and third arguments, which can be utilized if the developer desires a callback-driven request pattern instead of promises. Since it does use $http, promises are still very much integrated and available to the developer.

See also

  • The Using promises with $http recipe demonstrates how AngularJS promises are integrated with AJAX requests
  • The Using promises with Restangular recipe demonstrates how the popular third-party resource manager is extensively integrated with AngularJS promise conventions

Using promises with Restangular

Restangular, the extremely popular REST API extension to AngularJS, takes a much more promise-centric approach compared to $resource.

How to do it…

The Restangular REST API mapping will always return a promise. This is shown here:

(app.js)

angular.module('myApp', ['restangular'])
.controller('Ctrl', function($scope, Restangular) {
  Restangular
  .one('widget', 4)
  // get() will return a promise for the GET request
  .get()
  .then(
    function(data) {
      // consume response data in success handler
      $scope.status = 'One widget success!';
    },
    function(response) {
      // consume response message in error handler
      $scope.status = 'One widget failure!';
    }
  );
  
  // generally, the API mapping is stored in a variable,
  // and the promise-returning method will be invoked as needed
  var widgets = Restangular.all('widgets');
  
  // create the request promise
  widgets.getList()
  .then(function(widgets) {
    // success handler
    $scope.status = 'Many widgets success!';
  }, function() {
    // error handler
    $scope.status = 'Many widgets failure!';
  });
});

Since Restangular objects don't create promises until the request method is invoked, it is possible to chain Restangular route methods before creating the request promise, in order to match the nested URL structure. This can be done as follows:

// GET request to /widgets/6/features/11
Restangular
.one('widgets', 6)
.one('features', 11)
.get()
.then(function(feature) {
  // success handler
});

How it works…

Every Restangular object method can be chained to develop nested URL objects, and every request to a remote API through Restangular returns a promise. In conjunction with its flexible and extensible resource CRUD methods, it creates a powerful toolkit to communicate with REST APIs.

See also

  • The Using promises with $http recipe demonstrates how AngularJS promises are integrated with AJAX requests
  • The Using promises with $resource recipe discusses how ngRoute can be used as a promise-centric resource manager

Incorporating promises into native route resolves

AngularJS routing supports resolves, which allow you to demand that some work should be finished before the actual route change process begins. Routing resolves accept one or more functions, which can either return values or promise objects that it will attempt to resolve.

How to do it…

Resolves are declared in the route definition, as follows:

(app.js)

angular.module('myApp', ['ngRoute'])
.config(function($routeProvider){
  $routeProvider
  .when('/myUrl', {
    template: '<h1>Resolved!</h1>',
    // resolved values are injected by property name
    controller: function($log, myPromise, myData) {
      $log.log(myPromise, myData);
    },
    resolve: {
      // $q injected into resolve function
      myPromise: function($q) {
        var deferred = $q.defer()
          , promise = deferred.promise;
        deferred.resolve(123);
        return promise;
      },
      myData: function() {
        return 456;
      }
    }
  });
})
.controller('Ctrl', function($scope, $location) {
  $scope.navigate = function() {
    $location.url('myUrl')
  };
});

(index.html)

<div ng-app="myApp">
  <div ng-controller="Ctrl">
    <button ng-click="navigate()">Navigate!</button>
    <div ng-view></div>
  <div>
</div>

With this configuration, navigating to /myUrl will log 123, 456 and render the template.

How it works…

The premise behind route resolves is that the promises gather data or perform tasks that need to be done before the route changes and the controller is created. A resolved promise signals the router that the page is safe to be rendered.

The object provided to the route resolve evaluates the functions provided to it and consequently makes injectables available in the route controller.

There are several important details to keep in mind involving route resolves, which are as follows:

  • Route resolve functions that return raw values are not guaranteed to be executed until they are injected, but functions that return promises are guaranteed to have those promises get resolved or rejected before the route changes and the controller is initialized.
  • Route resolves can only be injected into controllers defined in the route definition. Controllers named in the template via ng-controller cannot have the route resolve dependencies injected into them.
  • Routes with a specified route controller but without a specified template will never initialize the route controller, but the route resolve functions will still get executed.
  • Route resolves will wait for either all the promises to get resolved or one of the promises to get rejected before proceeding to navigate to the URL.

There's more…

By definition, promises are not guaranteed to undergo a final state transition, and the AngularJS router diligently waits for promises to get resolved unless they get rejected. Therefore, if a promise never gets resolved, the route change will never occur and your application will appear to hang.

See also

  • The Implementing nested ui-router resolves recipe provides the details of basic and advanced strategies used to integrate promises into nested views and their accompanying resources

Implementing nested ui-router resolves

As you gain experience as an AngularJS developer, you will come to realize that the built-in router faculties are quite brittle in a number of ways—mainly that there can only be a single instance of ng-view for dynamic route templating. AngularUI provides a superb solution to this in ui-router, which allows nested states and views, named views, piecewise routing, and nested resolves.

How to do it…

The ui-router framework supports resolves for states in the same way that ngRoute does for routes. Suppose your application displayed individual widget pages that list the features each widget has, as well as individual pages for each widget's features.

State promise inheritance

Since nested states can be defined with relative state routing, you might encounter the scenario where the URL parameters are only available within the state in which they are defined. For this application, the child state has a need to use the widgetId and the featureId value in the child state controller. This can be solved with nested route promises, as shown here:

(app.js)

angular.module('myApp', ['ui.router'])
.config(function($stateProvider) {
  $stateProvider
  .state('widget', {
    url: '/widgets/:widgetId',
    template: 'Widget ID: {{ widgetId }} <div ui-view></div>',
    controller: function($scope, $stateParams, widgetId){
      // the widgetId is only available in this state due to
      // the :widgetId variable definition in the state url
      $scope.widgetId = $stateParams.widgetId;
    },
    resolve:{
      // the stateParam widget property is wrapped in a property
      // to enable it to be injected in child states
      widgetId: function($stateParams){
        return $stateParams.widgetId;
      }
    }
  })
  .state('widget.feature', {
    url: '/features/:featureId',
    template: 'Feature ID: {{ featureId }}',
    // widgetId can now be injected from the parent state
    controller: function($scope, $stateParams, widgetId){
      // both widgetId and featureId are made available
      // in this state controller
      $scope.featureId = $stateParams.featureId;
      $scope.widgetId = widgetId;
    }
  });
});

(index.html)

<div ng-app="myApp">
  <a ui-sref="widget({widgetId:6})">
    See Widget 6
  </a>
  <a ui-sref="widget.feature({widgetId: 6, featureId:11})">
    See Feature 11 of Widget 6
  </a>
  <div ui-view></div>
</div>

Here, the child state has access to the injected widgetId value through the inherited resolution defined in the parent state.

Single-state promise dependencies

A state's resolve promises have the ability to depend on one another, which allows you the convenience of requesting data without explicitly defining the order or dependence. This can be done as follows:

(app.js)

angular.module('myApp', ['ui.router'])
.config(function($stateProvider) {
  $stateProvider
  .state('widget', {
    url: '/widgets',
    template: 'Widget: {{ widget }} Features: {{ features }}',
    controller: function($scope, widget, features){
      // resolve promises are injectable in the route controller
      $scope.widget = widget;
      $scope.features = features;
    },
    resolve: {
      // standard resolve value promise definition
      widget: function() {
        return {
          name: 'myWidget'
        };
      },
      // resolve promise injects sibling promise
      features: function(widget) {
        return ['featureA', 'featureB'].map(function(feature) { 
          return widget.name+':'+feature;
        });
      }
    }
  });
});

(index.html)

<div ng-app="myApp">
  <a ui-sref="widget({widgetId:6})">See Widget 6</a>
  <div ui-view></div>
</div>

With this setup, navigating to /widgets will print the following:

Widget: {"name":"myWidget"} 
Features: ["myWidget:featureA","myWidget:featureB"]

How it works…

Route resolves effectively represent an amount of work that needs to be completed before a route change can happen. These units of work, represented in the resolve as promises, are able to be dependency injected anywhere in the route state construct, which allows you a great deal of flexibility. Since the route change will only occur once all promises have resolved, you are able to effectively chain the promises within the route by chaining them using dependency injection.

Note

Be careful with promise dependencies within routes. It is entirely possible to create circular dependencies with such types of dependent declarations.

See also

  • The Incorporating promises into native route resolves recipe demonstrates how vanilla AngularJS routing incorporates promises into the route life cycle
See also
See also
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.22.77.149