Inside my Angular
application I handle routes/states via ui-router
. If everything works - it is great. But what is a good way to handle errors occurring inside resolve
functions?
My current solution:
I have a to dedicated error
state (similar to the common 404.html
). Which looks like this:
// inside config()
.state('error', {
url: '/error',
controller: 'ErrorCtrl',
templateUrl: 'error.html' // displays an error message
})
If an error occurs inside resolve
I catch it via the broadcasted $stateChangeError
in m run
function:
angular.module('myModule').run(function($state) {
$rootScope.$on('$stateChangeError', function(event, toState, toParams, fromState, fromParams, error) {
event.preventDefault();
$state.go('error');
});
});
This works, but I want to change my error message inside my 'error.html'
dependent on the error. I don't want to pollute the $rootScope
and I want to do it in a ui-router
'esk way.
My current solution uses $stateParams
to the error data to my error
state, but I have to use JSONified query params for this and get a very ugly URL:
// inside config()
.state('error', {
url: '/error?data&status&config', // accept these three params
controller: 'ErrorCtrl',
templateUrl: 'error.html' // displays an error message
})
angular.module('myModule').run(function($state) {
$rootScope.$on('$stateChangeError', function(event, toState, toParams, fromState, fromParams, error) {
event.preventDefault();
$state.go('error', JSON.stringify(error)); // error has data, status and config properties
});
});
Question
Is there a different way how I can pass the error
object to my error
state without uglifying my URL? (Note: My error
object is complex and not just a simple string.)
A strategy that worked for me is to have the resolve
functions return a rejected promise with the error object:
$stateProvider.state('needs_authentication', {
url: '/auth',
resolve: {
user: function ($q, authService) {
if (authService.isAuthenticated()) {
...
}
else {
var errorObject = { code: 'NOT_AUTHENTICATED' };
return $q.reject(errorObject);
}
}
}
});
This lets your $stateChangeError
function be able to handle specific error conditions:
$rootScope.$on('$stateChangeError', function (evt, toState, toParams, fromState, fromParams, error) {
if (angular.isObject(error) && angular.isString(error.code)) {
switch (error.code) {
case 'NOT_AUTHENTICATED':
// go to the login page
$state.go('login');
break;
default:
// set the error object on the error state and go there
$state.get('error').error = error;
$state.go('error');
}
}
else {
// unexpected error
$state.go('error');
}
});
You could pass data through a service, that you should access in your error controller or in onEnter method of error state.
Or you could "enrich" your error state. Maybe it's NOT the angular way, but what I mean is:
$rootScope.$on('$stateChangeError', function (event, toState, toParams, fromState, fromParams, error) {
event.preventDefault();
$state.get('error').error = { code: 123, description: 'Exception stack trace' }
return $state.go('error');
});
And in your error state config:
.state('error', {
url: 'error',
resolve: {
errorObj: [function () {
return this.self.error;
}]
},
controller: 'ErrorCtrl',
templateUrl: 'error.html' // displays an error message
});
Hope this helps
You can remove the whole url: option and replace this with just a params: option.
.state('error', { abstract: false, templateUrl: templateFor('error'), controller: controllerFor('error'), params: { 'error': 'An error has occurred' }, resolve: { error: [ '$stateParams', function ($stateParams) { return $stateParams.error; } ] } })
Now there is no ugly url. and the controller can get the error as a parameter.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With