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.
Table of contents
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.
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.
- Observables and Async Pipe
- Identity Checking and Performance
- Web Components <ng-template> syntax
- <ng-container> and Observable Composition
- Advanced Rendering Patterns
- 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!