AngularJS: Timing Multiple resource Resolves in ngRoute and ui-router



Update 12/10/13: Hey, this article was picked up by Angular News. Thanks, Brian!

Both AngularUI Router and Angular's own ngRoute support the concept of a resolve, an optional map of dependencies which should be injected into a controller associated with a particular route or state. If any of these dependencies are promises, the router will wait for them all to be resolved (or one to be rejected) before the controller is instantiated. This can be a very useful feature when you want to get some data and be sure it's available to a controller before you do any processing.

Resolves can be services, or functions that return a promise, an object, or a primitive. We could return a promise by using Angular's $q service like this:

// example $stateProvider config in our main module config (using ui-router)

$stateProvider

// Other states...

.state('state2', {
url: '/state2',
templateUrl: 'page2.html',
controller: 'Page2Ctrl',
resolve: {
delayedData: function($q, $timeout) {

// Set up a promise to return
var deferred = $q.defer();

// Simulate an external request, this could be an $http.get() in a real app
$timeout(function() {

var myData = {message: 'This string is the external data.'};

deferred.resolve(myData);
}, 1000);

return deferred.promise;
}
}
});

demo: Example 1

Resolve a resource

To help you interact with RESTful server-side data sources, Angular provides the $resource service. Your resources can be injected into a resolve and you can return a resource invocation like this:

// example $stateProvider config in our main module config (using ui-router)

$stateProvider

// Other states...

.state('state2', {
url: '/state2',
templateUrl: 'page2.html',
controller: 'Page2Ctrl',
resolve: {
gistsData: function(Gists) { // Inject a resource named 'Gists'

return Gists.query();
}
}
});

Assuming a resource like this exists:

// Example resource - GitHub REST API

myApp.factory('Gists', ['$resource', function ($resource) {

return $resource('https://api.github.com/gists');
}]);

demo: Example 2

When using a resource in your resolve instead of a simple promise, you may be surprised that the resource resolves instantly to an empty object. This is actually the intended behavior, to facilitate data binding. From the docs:

invoking a resource object method immediately returns an empty reference (object or array depending on isArray). Once the data is returned from the server the existing reference is populated with the actual data...

 

The idea here is that you can bind a $scope model directly to the returned resource data and let Angular handle updating the view when the data is available. Done!

But, what if you need to do some processing on this data in the controller before its exposed to $scope? Or what if some of your controller logic depends on this data? Well, since angular resources are services, you could forgo the resolve and inject them into the controller directly. In the controller, you can invoke the resource as needed and wrap any data-dependent logic in the .then() method of the returned $promise. However, if your state or route change logic depends on this data, there is another option.

Resolve a resource's promise

You can make sure a resource is fully resolved before your route/state change by returning the original $promise contained in the object returned by the resource invocation instead of the object itself.

// example $stateProvider config in our main module config (using ui-router)

$stateProvider

// Other states...

.state('state2', {
url: '/state2',
templateUrl: 'page2.html',
controller: 'Page2Ctrl',
resolve: {
gistsData: function(Gists) { // Inject a resource named 'Gists'

var GistsData = Gists.query();

// Return the original promise inside the returned $resource object
// Since this is a true promise, the resolve will wait
return GistsData.$promise;
}
}
});

Assuming a resource like this exists:

// Example resource - GitHub REST API

myApp.factory('Gists', ['$resource', function ($resource) {

return $resource('https://api.github.com/gists');
}]);

demo: Example 3

Resolve multiple resource promises

And what if you want to fully resolve multiple resource promises before your route/state change? No problem, just use $q.all to wait for all resource promises to resolve.

// example $stateProvider config in our main module config (using ui-router)

$stateProvider

// Other states...

.state('state2', {
url: '/state2',
templateUrl: 'page2.html',
controller: 'Page2Ctrl',
resolve: {
delayedData: function($q, Gists, Meta) { // Inject resources named 'Gists' and 'Meta'

// Set up our resource calls
var GistsData = Gists.query();
var MetaData = Meta.get();

// Wait until both resources have resolved their promises, then return this promise
return $q.all([GistsData.$promise, MetaData.$promise]);
}
}
});

Assuming these resources exist:

// Example resources - GitHub REST API

myApp.factory('Gists', ['$resource', function ($resource) {

return $resource('https://api.github.com/gists');
}]);

myApp.factory('Meta', ['$resource', function ($resource) {

return $resource('https://api.github.com/meta');
}]);

demo: Example 4

Notes

Note that although we've used AngularUI Router to illustrate resolves, you can use ngRoute the same way. Also, remember that a promise may be rejected as well as resolved. Make sure to handle rejections (and progress, if desired) as necessary.

Tags

Comments