Write AngularJS like a pro. Angularjs Icon

Follow the ultimate AngularJS roadmap.

Resolve promises in Angular routes

Phase 1 of our Angular careers involved $http inside a Controller. Phase 2 involved abstracting into a Service and calling the Service inside a Controller. Phase 3 is the resolve property.

Let’s take a look what resolve gives us.

Phase 1: $http inside Controllers

Please don’t do this, this is bad design and business logic is riddled in the Controller. This is merely to demonstrate the first way we probably used $http:

function InboxCtrl($http) {
  this.messages = [];
  $http.get('/messages').then(function (response) {
    this.messages = response.data;
  }.bind(this));
}

angular
  .module('app', [])
  .controller('InboxCtrl', InboxCtrl);

We’ll then probably have an ng-repeat that populates the DOM from this.messages once it has updated and the $digest has looped.

Phase 2: Service abstraction

After realising that using $http in Controllers was a bad idea, we then kept things DRY, testable/reusable and abstracted out into the correct design pattern.

// create an awesome InboxService to fetch our messages
function InboxService($http) {
  function getMessages() {
    return $http.get('/messages').then(function (response) {
      return response.data;
    });
  }
  return {
    getMessages: getMessages
  };
}

// inject InboxService and bind the 
// response to `this.messages`
function InboxCtrl(InboxService) {
  this.messages = [];
  InboxService.getMessages().then(function (response) {
    this.messages = response;
  }.bind(this));
}

angular
  .module('app', [])
  .controller('InboxCtrl', InboxCtrl)
  .factory('InboxService', InboxService);

This is pretty good, but it can be done a lot better. We’re still instantiating our Controllers, then making the call to InboxService.getMessages(). Would it not be better to first get the data, then instantiate the Controller? Kerching, hello resolve.

Phase 3: Resolve property on routes

For these examples I’m going to be using ui-router, so we’ll be using $stateProvider instead of $routeProvider, but the syntax is exactly the same on both so you’re golden to use it wherever.

We add a resolve property to each route we want to resolve data before Controller instantiation:

$stateProvider
  .state('inbox', {
    url: '/inbox',
    templateUrl: 'partials/inbox.html',
    controller: 'InboxCtrl as vm',
    resolve: {
      messages: function (InboxService) {
        return InboxService.getMessages();
      }
    }
  });

Let’s take a closer inspection and annotate the areas of interest that we need to learn:

$stateProvider
  .state('inbox', {
    ...
    // Use an Object as the value of `resolve`
    resolve: {
      // create an Object property called "messages"
      // which will later be used for Dependency Injection
      // inside our Controller. Inject any Services we need as usual.
      messages: function (InboxService) {
        // Return our Service call, that returns a Promise
        return InboxService.getMessages();
      }
    }
  });

If the fact we called our Object property messages for dependency injection purposes hasn’t clicked yet, it will now. We take this messages property name, and inject it into our Controller. The result of the Service call will be bound to the value (in this case the list of messages), which means our Controller is extremely slim, and is only instantiated once that data is there. This makes binding to our Controller extremely lightweight and simple.

Angular Directives In-Depth eBook Cover

Free eBook

Directives, simple right? Wrong! On the outside they look simple, but even skilled Angular devs haven’t grasped every concept in this eBook.

  • Green Tick Icon Observables and Async Pipe
  • Green Tick Icon Identity Checking and Performance
  • Green Tick Icon Web Components <ng-template> syntax
  • Green Tick Icon <ng-container> and Observable Composition
  • Green Tick Icon Advanced Rendering Patterns
  • Green Tick Icon Setters and Getters for Styles and Class Bindings
function InboxCtrl(messages) {
  this.messages = messages;
}

You can also have multiple Object properties on a resolve:

$stateProvider
  .state('inbox', {
    ...
    resolve: {
      user: function (UserService) {
        return UserService.getUser();
      },
      messages: function (InboxService) {
        return InboxService.getMessages();
      }
    }
  });

And inject user and messages where necessary.

Coupling routing logic with Controllers

In my Angular styleguide, I recommend using a .resolve property on Controllers, to contain the necessary resolve logic, and use hoisting to achieve the desired effects.

For example, I have this Object bound to the route:

// router.js
resolve: {
  messages: function (InboxService) {
    return InboxService.getMessages();
  }
}

And this code inside the Controller.

// InboxCtrl.js
function InboxCtrl(messages) {
  this.messages = messages;
}

It’s easy to lose sight on what messages means whilst inspecting this file (a made-up InboxCtrl.js), as well as the overhead of having to go back to the pseudo router.js file, and scrolling through all our routes and resolve logic that continues to grow to make any changes.

The solution? Add a .resolve property to each Controller, full file example:

// InboxCtrl.js
function InboxCtrl(messages) {
  this.messages = messages;
}

InboxCtrl.resolve = {
  messages: function (InboxService) {
    return InboxService.getMessages();
  }
}

angular
  .module('app')
  .controller('InboxCtrl', InboxCtrl);

And the router.js:

$stateProvider
  .state('inbox', {
    url: '/inbox',
    templateUrl: 'partials/inbox.html',
    controller: 'InboxCtrl as vm',
    resolve: InboxCtrl.resolve
  });

Hoisting reliance

This technique relies on hoisting. This means do not wrap all your files inside IIFE’s, function scopes, closures or whatever you’re calling it - the InboxCtrl needs to be available in the same scope so InboxCtrl.resolve can be accessed on the router.

“I wrap all my files inside an IIFE”. Don’t, it’s a bit of a pointless and very manual venture. Wrap each module you create inside an IIFE or the entire application. You shouldn’t have naming collisions so one IIFE should be golden to protect your app from the “dreaded” global state.

DI resolve naming conventions

I’m not happy with simply injecting messages in this case, as route resolving is a special use case when injecting into Controllers, so from a developer perspective I’d like to know what dependencies are bound to the router’s resolve Object without even looking at the resolve = {} Object.

Saying that, I’m also not settled on a specific convention, however I do consider prefixing dependencies a potential idea. Something like:

// InboxCtrl.js
function InboxCtrl(_messages) {
  this.messages = _messages;
}

InboxCtrl.resolve = {
  _messages: function (InboxService) {
    return InboxService.getMessages();
  }
}

angular
  .module('app')
  .controller('InboxCtrl', InboxCtrl);

Nothing huge from the naming convention side, however could be an important decision when deciding for your application or team.

Minification

To play nicely with minification you’ll need to annotate in the following ways. If you’re using ng-annotate then @ngInject is your friend here.

// InboxCtrl.js
function InboxCtrl(_messages) {
  this.messages = _messages;
}

InboxCtrl.$inject = ['_messages'];

InboxCtrl.resolve = {
  _messages: ['InboxService', function (InboxService) {
    return InboxService.getMessages();
  }]
}

angular
  .module('app')
  .controller('InboxCtrl', InboxCtrl);

Thank you for reading!

Learn Angular the right way.

The most complete guide to learning Angular ever built.
Trusted by 82,951 students.

Todd Motto

with Todd Motto

Google Developer Expert icon Google Developer Expert

Related blogs 🚀

Free eBooks:

Angular Directives In-Depth eBook Cover

JavaScript Array Methods eBook Cover

NestJS Build a RESTful CRUD API eBook Cover