Making promises with AngularJS

I recently had a need to wait on multiple resource queries in an AngularJS controller and then do something with the returned data only after all of the data had been returned. A naive implementation of this might look something like this.

User = $resource '/user/:id/', { id: '@id' }
Accounts = $resource '/accounts/:id', { id: '@id' }
@user = User.get { id: $routeParams.id }, (user) =>
  @account = Accounts.query { userId: $routeParams.id }, (accounts) =>
    # Do work after user and accounts have loaded here.

Note: The above is based ont the “controller as” approach but you can substitute $scope just as well

This is both ugly from a nesting perspective and it means that we are waiting on one request before we make another when our browser is perfectly capable of making both requests simultaneously.

AngularJS includes a promises service called $q which is actually used underneath the hood in a lot of its built-in services like $http and $resource. However, it often gets overlooked when actually building things. One of the things you can do is use it to create a promise of promises. Something like this:

User = $resource '/user/:id/', { id: '@id' }
Accounts = $resource '/accounts/:id', { id: '@id' }
@user = User.get({ id: $routeParams.id }).$promise
@accounts: Accounts.query({ userId: $routeParams.id }).$promise
waitUntilLoaded = $q.all
  user: @user
  accounts: @accounts
.then (results) =>
  # Do work with results.user and results.accounts here.

Note: $q.all can take an object or array, it will be passed to the then callback with the values resolved by the promises in place of the promises themselves.

Defining our own “promise of promises” has the added benefit of being re-usable. The then callback will execute immediately assuming the promise has been resolved, otherwise it will wait. This is particularly handy if we have a $watch in our controller that shouldn’t execute until after the data is loaded.

$scope.$watch 'someUIValue', (value) =>
  waitUntilLoaded.then =>
  # Now we can ensure this code will only # execute after
  # the waitUntilLoaded promises have been resolved.

This is a much better design than the alternative of setting up the $watch inside of the then because this way if we end up with some sort of “race condition” where the watch gets fired slightly before the promises are resolved we will still ensure that we fire the watch as soon as the appropriate data is loaded.

What about router resolves?

There is an alternative approach to making controllers wait on promises which is using the Angular router’s resolves. However, I’ve found this interface clunky and don’t like the idea of dealing with these sorts of dependencies inside my routing table. Some of the patterns of dealing with this that have been suggested are quite unsatisfactory and seem highly dependent on how you organize your code and define your classes.

I’d like to use resolves but I don’t see why it should be dependent on the routing instead of just a dependency of the controller. I’m considering some alternative patterns but have not yet finalized anything that works yet. I’ll definitely follow up on this post if and when I do. For now, using $q satisfies my needs until I find a way to use resolves that I like.

comments powered by Disqus