Resolve start-up logic for a controller in an activate
function.
Why?: Placing start-up logic in a consistent place in the controller makes it easier to locate, more consistent to test, and helps avoid spreading out the activation logic across the controller.
Why?: The controller activate
makes it convenient to re-use the logic for a refresh for the controller/View, keeps the logic together, gets the user to the View faster, makes animations easy on the ng-view
or ui-view
, and feels snappier to the user.
Note: If you need to conditionally cancel the route before you start using the controller, use a route resolve instead.
/* avoid */
function AvengersController(dataservice) {
var vm = this;
vm.avengers = [];
vm.title = 'Avengers';
dataservice.getAvengers().then(function(data) {
vm.avengers = data;
return vm.avengers;
});
}
/* recommended */
function AvengersController(dataservice) {
var vm = this;
vm.avengers = [];
vm.title = 'Avengers';
activate();
////////////
function activate() {
return dataservice.getAvengers().then(function(data) {
vm.avengers = data;
return vm.avengers;
});
}
}
When a controller depends on a promise to be resolved before the controller is activated, resolve those dependencies in the $routeProvider
before the controller logic is executed. If you need to conditionally cancel a route before the controller is activated, use a route resolver.
Use a route resolve when you want to decide to cancel the route before ever transitioning to the View.
Why?: A controller may require data before it loads. That data may come from a promise via a custom factory or $http. Using a route resolve allows the promise to resolve before the controller logic executes, so it might take action based on that data from the promise.
Why?: The code executes after the route and in the controller’s activate function. The View starts to load right away. Data binding kicks in when the activate promise resolves. A “busy” animation can be shown during the view transition (via ng-view
or ui-view
)
Note: The code executes before the route via a promise. Rejecting the promise cancels the route. Resolve makes the new view wait for the route to resolve. A “busy” animation can be shown before the resolve and through the view transition. If you want to get to the View faster and do not require a checkpoint to decide if you can get to the View, consider the controller activate
technique instead.
/* avoid */
angular
.module('app')
.controller('AvengersController', AvengersController);
function AvengersController(movieService) {
var vm = this;
// unresolved
vm.movies;
// resolved asynchronously
movieService.getMovies().then(function(response) {
vm.movies = response.movies;
});
}
/* better */
// route-config.js
angular
.module('app')
.config(config);
function config($routeProvider) {
$routeProvider
.when('/avengers', {
templateUrl: 'avengers.html',
controller: 'AvengersController',
controllerAs: 'vm',
resolve: {
moviesPrepService: function(movieService) {
return movieService.getMovies();
}
}
});
}
// avengers.js
angular
.module('app')
.controller('AvengersController', AvengersController);
AvengersController.$inject = ['moviesPrepService'];
function AvengersController(moviesPrepService) {
var vm = this;
vm.movies = moviesPrepService.movies;
}
Note: The example below shows the route resolve points to a named function, which is easier to debug and easier to handle dependency injection.
/* even better */
// route-config.js
angular
.module('app')
.config(config);
function config($routeProvider) {
$routeProvider
.when('/avengers', {
templateUrl: 'avengers.html',
controller: 'AvengersController',
controllerAs: 'vm',
resolve: {
moviesPrepService: moviesPrepService
}
});
}
function moviesPrepService(movieService) {
return movieService.getMovies();
}
// avengers.js
angular
.module('app')
.controller('AvengersController', AvengersController);
AvengersController.$inject = ['moviesPrepService'];
function AvengersController(moviesPrepService) {
var vm = this;
vm.movies = moviesPrepService.movies;
}
Note: The code example's dependency on `movieService` is not minification safe on its own. For details on how to make this code minification safe, see the sections on [dependency injection](#manual-annotating-for-dependency-injection) and on [minification and annotation](#minification-and-annotation).
The catch
block of a promise must return a rejected promise to maintain the exception in the promise chain.
Always handle exceptions in services/factories.
Why?: If the catch
block does not return a rejected promise, the caller of the promise will not know an exception occurred. The caller’s then
will execute. Thus, the user may never know what happened.
Why?: To avoid swallowing errors and misinforming the user.
Note: Consider putting any exception handling in a function in a shared module and service.
/* avoid */
function getCustomer(id) {
return $http.get('/api/customer/' + id)
.then(getCustomerComplete)
.catch(getCustomerFailed);
function getCustomerComplete(data, status, headers, config) {
return data.data;
}
function getCustomerFailed(e) {
var newMessage = 'XHR Failed for getCustomer'
if (e.data && e.data.description) {
newMessage = newMessage + '\n' + e.data.description;
}
e.data.description = newMessage;
logger.error(newMessage);
// ***
// Notice there is no return of the rejected promise
// ***
}
}
/* recommended */
function getCustomer(id) {
return $http.get('/api/customer/' + id)
.then(getCustomerComplete)
.catch(getCustomerFailed);
function getCustomerComplete(data, status, headers, config) {
return data.data;
}
function getCustomerFailed(e) {
var newMessage = 'XHR Failed for getCustomer'
if (e.data && e.data.description) {
newMessage = newMessage + '\n' + e.data.description;
}
e.data.description = newMessage;
logger.error(newMessage);
return $q.reject(e);
}
}