UI Router: how to automatically redirect from inva

2019-09-03 06:32发布

I'm building a multistep form in the style of scotch.io. I used resolve to restrict access to next steps until the current step's object exists on the service model (side point – is there a DRYer way to do this?).

The States

Here's are my top-level routes:

angular
  .module('donate')
  .config(routerConfig);

/** @ngInject */
function routerConfig($stateProvider, $urlRouterProvider) {
  $stateProvider
    .state('root', {
      url: '',
      abstract: true,
      views: {
        'donate': {
          controller: 'DonateController as donate',
          templateUrl: 'app/components/donate/donate.html'
        },
        'stories': {
          controller: 'StoriesController as stories',
          templateUrl: 'app/components/stories/views/stories.html'
        }
      }
    });

  $urlRouterProvider.otherwise('/');
}

And these are the relevant child routes.

angular
  .module('donate')
  .config(function($stateProvider) {
    $stateProvider
      .state('root.amount', {
        url: '/',
        controller: 'AmountController as amount',
        templateUrl: 'app/components/donate/views/amount.html'
      })
      .state('root.card', {
        url: '/card',
        resolve: {
          amount: function($q,DonateService){
            if(!DonateService.amount){
              var errorObject = {code:'NO_AMOUNT'};
              return $q.reject(errorObject);
            }
          }
        },
        controller: 'CardController as card',
        templateUrl: 'app/components/donate/views/card.html'
        })
      .state('root.billing', {
        url: '/billing-info',
        resolve: {
          card: function($q,DonateService){
            if(!DonateService.card){
              var errorObject = {code:'NO_CARD'};
              return $q.reject(errorObject);
            }
          }
        },
        controller: 'BillingController as billing',
        templateUrl: 'app/components/donate/views/billing.html'
        })
      .state('root.confirm', {
        url:'/confirm',
        resolve: {
          billing: function($q,DonateService){
            if(!DonateService.email){
              var errorObject = {code:'NO_BILLING_INFO'};
              return $q.reject(errorObject);
            }
          }
        },
        controller: 'ConfirmController as confirm',
        templateUrl: 'app/components/donate/views/confirm.html'
      })
      .state('root.success', {
        url:'/success',
        controller: 'SuccessController as success',
        templateUrl: 'app/components/donate/views/success.html'
      })
      .state('root.error', {
        url:'/error',
        controller: 'ErrorController as error',
        templateUrl: 'app/components/donate/views/error.html'
      });
  });

A little bit of error reporting

This switch on the parent controller prevents users from jumping to the next step by clicking on the step nav when the the children's reject the promise set up above.

angular
  .module('donate')
  .controller('DonateController', DonateController);

DonateController.$inject = ['DonateService', '$rootScope', '$state', '$log'];

/* @ngInject */
function DonateController(DonateService, $rootScope, $state, $log) {
  var vm = this;
  vm.DonateService = DonateService;

  $rootScope.$on('$stateChangeError', stateError);

  function stateError (e, toState, toParams, fromState, fromParams, error) {
    switch (error.code) {
      case 'NO_AMOUNT':
        $log.debug('State Error:', error.code);
        $state.go('root.amount');
        break;
      case 'NO_CARD':
        $log.debug('State Error:', error.code);
        $state.go('root.card');
        break;
      case 'NO_BILLING_INFO':
        $log.debug('State Error:', error.code);
        $state.go('root.billing');
        break;
      default:
        $log.debug('State Error:', error.code);
        $state.get('error').error = error;
        $state.go('root.amount');
    }
  }
}

The Question

This works swimmingly. When a user tries to click on an inaccessible step's nav pill, the console dutifully belches out an error message or three. But one problem remains: Users can browse (via the URL bar) to /card, /billing-info and /confirm and they'll be greeted with a blank view, with no console output to speak of.

How do I hook it so that users who browse to any route they aren't supposed to be on are bounced back to the beginning (or, even better, the last completed state)?

1条回答
Deceive 欺骗
2楼-- · 2019-09-03 07:07

Moving $rootScope.$on() to the runBlock solved the problem.

查看更多
登录 后发表回答