Могу ли я остановить переход в следующее состояние в onExit?

У меня есть два состояния: A и B.

  • Когда я выхожу из состояния A, нажав кнопку закрытия, я делаю переход на экран A, используя $state.go, чтобы указать B (экран B)
  • Когда я выхожу из состояния A, нажимая на кнопку назад браузера на экране A, я делаю переход в состояние B (экран B) при изменении URL-адреса браузера.

В onExit экрана A я делаю чек, и если это не открывается диалоговое окно с ошибкой, нажатие кнопки Закрыть в диалоговом окне ошибки возвращает сбойное обещание onExit

Однако onExit все еще идет вперед, и я иду на экран B

Можно ли остановить переход из состояния A (экран A) в состояние B (экран B), если что-то не работает в onExit?

Ответ 1

Вы можете достичь своей цели, выполнив $stateChangeStart обработчик событий, в котором вы можете отменить переход состояния (event.preventDefault();) при необходимости.

Ниже приведен пример, где checkBox имитирует условие перехода. В этом случае при изменении состояния (state.go и navigation) он открывает модальный запрос, чтобы пользователь принял/отклонил блокировку перехода (другое симуляция некоторой проверки проверки):

var myApp = angular.module('myApp', ['ui.router','ui.bootstrap']);

myApp.config(function($stateProvider, $urlRouterProvider) {

  $urlRouterProvider.otherwise('/state_a');

  $stateProvider
    .state('state_a', {
      url: '/state_a',
      templateUrl: 'stateA.html',
      onEnter: function() {
        console.log("onEnter stateA");
      },
      onExit: function($rootScope, $state) {
        console.log("onExit stateA: "+$rootScope.chk.transitionEnable);
        
      },
      controller: function($scope, $state) {
        $scope.goToStateB = function() {
          $state.go("state_b");
        }
      }
    })
    .state('state_b', {
      url: '/state_b',
      templateUrl: 'stateB.html',
      onEnter: function() {
        console.log("onEnter stateB");
      },
      onExit: function() {
        console.log("onExit stateB");
      },
      controller: function($scope) {
        
      }
    });

});

myApp.controller('mainCtrl', function($rootScope, $scope, $uibModal, $state) {

  $rootScope.chk = {};
  $rootScope.chk.transitionEnable = true;
  
  $rootScope.$on('$stateChangeStart',
    function(event, toState, toParams, fromState, fromParams, options) {
      if (!$rootScope.chk.transitionEnable) {
        event.preventDefault();
        $scope.toState = toState;
        $scope.open();
      } else {
        console.log("$stateChangeStart: "+toState.name);
      }
  })
  
  $scope.open = function () {

    var modalInstance = $uibModal.open({
      animation: $scope.animationsEnabled,
      templateUrl: 'myModal.html',
      scope: $scope,
      controller: function($uibModalInstance, $scope) {
          $scope.ok = function () {
            $uibModalInstance.close('ok');
          };
        
          $scope.cancel = function () {
            $uibModalInstance.dismiss('cancel');
          };
      },
      size: 'sm'
    });

    modalInstance.result.then(function (value) {
      console.info('Modal closed: ' + value);
        
    }, function () {
      console.info('Modal dismissed');
      $rootScope.chk.transitionEnable = true;
      $state.go($scope.toState.name);
    });
  };

});
<!DOCTYPE html>
<html>

<head>
  <link href="//netdna.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">
  <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular.js"></script>
  <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.9/angular-animate.js"></script>
  <script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-1.1.1.js"></script>
  <script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.17/angular-ui-router.min.js"></script>
  <script src="app.js"></script>
</head>

<body ng-app="myApp" ng-controller="mainCtrl">

  <nav class="navbar navbar-inverse" role="navigation">
    <div class="navbar-header">
      <a class="navbar-brand" ui-sref="#">AngularUI Router</a>
    </div>
    <ul class="nav navbar-nav">
      <li><a ui-sref="state_a">State A</a></li>
      <li><a ui-sref="state_b">State B</a></li>
    </ul>
  </nav>

  <div class="container">
    <div ui-view></div>
  </div>

  <script type="text/ng-template" id="myModal.html">
    <div class="modal-header">
      <h3 class="modal-title">Title</h3>
    </div>
    <div class="modal-body">
      Transition to <b>{{toState.name}}</b> is disabled, accept or ignore?
    </div>
    <div class="modal-footer">
      <button class="btn btn-primary" type="button" ng-click="ok()">Accept</button>
      <button class="btn btn-warning" type="button" ng-click="cancel()">Ignore</button>
    </div>
  </script>
 
  <script type="text/ng-template" id="stateA.html">
  <div class="jumbotron text-center">
    <h1>state A</h1>
    <p>Example</p>
    
    <a class="btn btn-primary" ng-click="goToStateB()">Goto State B</a>
    <a class="btn btn-danger">Do nothing...</a>
  </div>
  <div class="checkbox">
    <label>
      <input type="checkbox" ng-model="chk.transitionEnable"> Enable transition
    </label>
    <pre>chk.transitionEnable = {{chk.transitionEnable}}</pre>
  </div>
  </script>
  
  <script type="text/ng-template" id="stateB.html">
<div class="jumbotron text-center">
    <h1>state B</h1>
    <p>The page... </p>
</div>

<div class="container">
    Bla bla
  <div class="checkbox">
    <label>
      <input type="checkbox" ng-model="chk.transitionEnable"> Enable transition
    </label>
    <pre>chk.transitionEnable = {{chk.transitionEnable}}</pre>
  </div>
</div>
  </script>

</body>

</html>

Ответ 3

Подход

@beaver определенно хорошо работает, если логика решает переход к state B находится в контроллере state A.

Однако если это так, что переход состояния к state B требует, чтобы предварительное условие было истинным (независимо от текущего состояния), то с помощью ui -router Resolve может быть более чистым решением.

Бизнес-логика предварительного условия может жить в отдельной службе.

Эта настройка также позволяет контроллеру state B получать разрешенное значение, если это необходимо. И поскольку функция разрешения использует promises, процесс может быть асинхронным.

    angular.module("yourAppModule")
      .service('StateB_helperService', [
        '$q',

        function($q) {
          this.allowTransition = function() {
            var deferred = $q.defer();
            // 
            // ... state transition logic ...
            // 
            // resolves the promise to allow the state transition
            // rejects otherwise
            // 

            return deferred.promise;
          }
        }
      ])

...

angular.module("yourAppModule")
  .config([
    '$stateProvider',

    function($stateProvider) {

      $stateProvider.state('state_b', {
        templateUrl: "...",
        resolve: {
          stateB_PreCondition: [

            'StateB_helperService',

            function(StateB_helperService) {
              return StateB_helperService.allowTransition();
            }
          ]
        }
      })
    }
  ])