Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling error in ui-routers resolve function? (aka $stateChangeError) Passing data to error state?

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.)

like image 625
Pipo Avatar asked Apr 08 '14 12:04

Pipo


3 Answers

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');
    }
});
like image 198
Miller Avatar answered Oct 24 '22 02:10

Miller


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

like image 26
wilver Avatar answered Oct 24 '22 03:10

wilver


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.

like image 4
Peter Marshall Avatar answered Oct 24 '22 03:10

Peter Marshall