Подтвердить что ты не робот

Stop angular -ui-router навигации до тех пор, пока не будет разрешено обещание

Я хочу предотвратить некоторое мерцание, которое происходит, когда rails разрабатывает таймаут, но angular не знает до следующей ошибки авторизации от ресурса.

Что происходит, так это то, что шаблон визуализируется, некоторые вызовы ajax для ресурсов происходят, а затем мы перенаправляемся на rails для входа в систему. Я предпочел бы делать ping для рельсов при каждом изменении состояния, и если сеанс rails истек, тогда я немедленно перенаправляю ПЕРЕД тем, как будет отображаться шаблон.

ui-router имеет решение, которое может быть помещено на каждый маршрут, но это вообще не выглядит СУХОЙ.

У меня это есть. Но обещание не разрешается, пока государство уже не перешло.

$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams){
        //check that user is logged in
        $http.get('/api/ping').success(function(data){
          if (data.signed_in) {
            $scope.signedIn = true;
          } else {
            window.location.href = '/rails/devise/login_path'
          }
        })

    });

Как я могу прервать переход состояния, прежде чем новый шаблон будет визуализирован, на основе результата обещания?

4b9b3361

Ответ 1

Я знаю, что это очень поздно для игры, но я хотел бросить свое мнение там и обсудить, что я считаю, отличный способ "приостановить" изменение состояния. В документации по angular -ui-router любой член объекта "разрешить" состояния, являющегося обещанием, должен быть разрешен до того, как состояние закончит загрузку. Таким образом, мое функциональное (хотя и не очищенное и усовершенствованное) решение заключается в том, чтобы добавить обещание к объекту разрешения "toState" на "$ stateChangeStart":

например:

$rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
    toState.resolve.promise = [
        '$q',
        function($q) {
            var defer = $q.defer();
            $http.makeSomeAPICallOrWhatever().then(function (resp) {
                if(resp = thisOrThat) {
                    doSomeThingsHere();
                    defer.resolve();
                } else {
                    doOtherThingsHere();
                    defer.resolve();
                }
            });
            return defer.promise;
        }
    ]
});

Это гарантирует, что изменение состояния будет выполнено для обетования, которое будет разрешено, которое будет выполнено после завершения вызова API, и будут приняты все решения, основанные на возврате из API. Я использовал это для проверки состояния входа на серверной стороне, прежде чем разрешить навигацию к новой странице. Когда вызов API разрешает, я либо использую "event.preventDefault()", чтобы остановить исходную навигацию, а затем перейти на страницу входа (окружающий весь блок кода с if state.name!= "Login" ) или разрешить пользователю чтобы продолжить, просто разрешив отложенное обещание вместо того, чтобы пытаться использовать байпасное логическое значение и предотвратитьDefault().

Хотя я уверен, что оригинальный плакат уже давно выяснил их проблему, я действительно надеюсь, что это поможет кому-то еще там.

ИЗМЕНИТЬ

Я решил, что не хочу вводить людей в заблуждение. Вот какой код должен выглядеть, если вы не уверены, что ваши состояния имеют объекты разрешения:

$rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
    if (!toState.resolve) { toState.resolve = {} };
    toState.resolve.pauseStateChange = [
        '$q',
        function($q) {
            var defer = $q.defer();
            $http.makeSomeAPICallOrWhatever().then(function (resp) {
                if(resp = thisOrThat) {
                    doSomeThingsHere();
                    defer.resolve();
                } else {
                    doOtherThingsHere();
                    defer.resolve();
                }
            });
            return defer.promise;
        }
    ]
});

РЕДАКТИРОВАТЬ 2

чтобы заставить это работать для состояний, которые не имеют определения разрешения, вам нужно добавить это в app.config:

   var $delegate = $stateProvider.state;
        $stateProvider.state = function(name, definition) {
            if (!definition.resolve) {
                definition.resolve = {};
            }

            return $delegate.apply(this, arguments);
        };

Выполнение if (!toState.resolve) { toState.resolve = {} }; в stateChangeStart не работает, я думаю, что ui-router не принимает разрешение dict после его инициализации.

Ответ 2

Я считаю, что вы ищете event.preventDefault()

Примечание. Используйте функцию event.preventDefault(), чтобы предотвратить переход.

$scope.$on('$stateChangeStart', 
function(event, toState, toParams, fromState, fromParams){ 
        event.preventDefault(); 
        // transitionTo() promise will be rejected with 
        // a 'transition prevented' error
})

Хотя я бы, вероятно, использовал resolve в конфигурации состояния, поскольку @charlietfl предложил

ИЗМЕНИТЬ

поэтому у меня была возможность использовать preventDefault() в событии изменения состояния, и вот что я сделал:

.run(function($rootScope,$state,$timeout) {

$rootScope.$on('$stateChangeStart',
    function(event, toState, toParams, fromState, fromParams){

        // check if user is set
        if(!$rootScope.u_id && toState.name !== 'signin'){  
            event.preventDefault();

            // if not delayed you will get race conditions as $apply is in progress
            $timeout(function(){
                event.currentScope.$apply(function() {
                    $state.go("signin")
                });
            },300)
        } else {
            // do smth else
        }
    }
)

}

ИЗМЕНИТЬ

Более новая документация включает пример того, как следует sync() продолжать preventDefault после вызова preventDefault, но exaple при условии, что там используется $locationChangeSuccess событие, которое для меня и комментаторов не работает, вместо этого используйте $stateChangeStart, как в приведенном ниже примере, взятом из документов с обновленным событием:

angular.module('app', ['ui.router'])
    .run(function($rootScope, $urlRouter) {
        $rootScope.$on('$stateChangeStart', function(evt) {
            // Halt state change from even starting
            evt.preventDefault();
            // Perform custom logic
            var meetsRequirement = ...
            // Continue with the update and state transition if logic allows
            if (meetsRequirement) $urlRouter.sync();
        });
    });

Ответ 3

Вот мое решение этой проблемы. Он работает хорошо и находится в духе некоторых других ответов здесь. Он просто немного почищен. Я устанавливаю пользовательскую переменную, называемую stateChangeBypass, в корневую область, чтобы предотвратить бесконечное циклирование. Я также проверяю, является ли состояние "login", и если да, это всегда разрешено.

function ($rootScope, $state, Auth) {

    $rootScope.$on('$stateChangeStart', function (event, toState, toParams) {

        if($rootScope.stateChangeBypass || toState.name === 'login') {
            $rootScope.stateChangeBypass = false;
            return;
        }

        event.preventDefault();

        Auth.getCurrentUser().then(function(user) {
            if (user) {
                $rootScope.stateChangeBypass = true;
                $state.go(toState, toParams);
            } else {
                $state.go('login');
            }
        });

    });
}

Ответ 4

как $urlRouter.sync() не работает с stateChangeStart, здесь альтернатива:

    var bypass;
    $rootScope.$on('$stateChangeStart', function(event,toState,toParams) {
        if (bypass) return;
        event.preventDefault(); // Halt state change from even starting
        var meetsRequirement = ... // Perform custom logic
        if (meetsRequirement) {  // Continue with the update and state transition if logic allows
            bypass = true;  // bypass next call
            $state.go(toState, toParams); // Continue with the initial state change
        }
    });

Ответ 5

Чтобы добавить к существующим ответам, у меня была такая же проблема; мы использовали обработчик событий в корневой области для прослушивания $stateChangeStart для моей обработки разрешений. К сожалению, у этого был неприятный побочный эффект, изредка вызывающий бесконечные дайджесты (не знаю, почему, код не был написан мной).

Решение, с которым я столкнулся, которого не хватает, - это всегда предотвращать переход с event.preventDefault(), а затем определять, зарегистрирован ли пользователь через асинхронный вызов. После проверки этого, используйте $state.go для перехода в новое состояние. Важный бит, однако, заключается в том, что вы установили для свойства notify в параметрах $state.go значение false. Это предотвратит переход состояний от другого $stateChangeStart.

 event.preventDefault();
 return authSvc.hasPermissionAsync(toState.data.permission)
    .then(function () {
      // notify: false prevents the event from being rebroadcast, this will prevent us
      // from having an infinite loop
      $state.go(toState, toParams, { notify: false });
    })
    .catch(function () {
      $state.go('login', {}, { notify: false });
    });

Это не очень желательно, но это необходимо для меня из-за того, что загружаются разрешения в этой системе; если бы я использовал синхронный hasPermission, разрешения могли не быть загружены во время запроса на страницу.:( Может быть, мы могли бы спросить u-router для метода continueTransition в событии?

authSvc.hasPermissionAsync(toState.data.permission).then(continueTransition).catch(function() {
  cancelTransition();
  return $state.go('login', {}, { notify: false });
});

Ответ 6

Метод on возвращает a deregistration function for this listener.

Итак, вот что вы можете сделать:

var unbindStateChangeEvent = $scope.$on('$stateChangeStart', 
  function(event, toState, toParams) { 
    event.preventDefault(); 

    waitForSomething(function (everythingIsFine) {
      if(everythingIsFine) {
        unbindStateChangeEvent();
        $state.go(toState, toParams);
      }
    });
});

Ответ 7

Мне действительно нравится предлагаемое решение TheRyBerg, так как вы можете делать все в одном месте и не слишком много странных трюков. Я обнаружил, что есть способ улучшить его еще больше, так что вам не нужен stateChangeBypass в корнеплодах. Основная идея заключается в том, что вы хотите что-то инициализировать в своем коде, прежде чем ваше приложение сможет "запустить". Тогда, если вы просто помните, если он был инициализирован или нет, вы можете сделать это следующим образом:

rootScope.$on("$stateChangeStart", function (event, toState, toParams, fromState) {

    if (dataService.isInitialized()) {
        proceedAsUsual(); // Do the required checks and redirects here based on the data that you can expect ready from the dataService
    } 
    else {

        event.preventDefault();

        dataService.intialize().success(function () {
                $state.go(toState, toParams);
        });
    }
});

Тогда вы можете просто помнить, что ваши данные уже инициализированы в службе так, как вам нравится, например:

function dataService() {

    var initialized = false;

    return {
        initialize: initialize,
        isInitialized: isInitialized
    }

    function intialize() {

        return $http.get(...)
                    .success(function(response) {
                            initialized=true;
                    });

    }

    function isInitialized() {
        return initialized;
    }
};

Ответ 8

Вы можете захватить параметры перехода из $stateChangeStart и закрепить их в службе, а затем переустановить переход после того, как вы обратитесь к логину. Вы также можете посмотреть https://github.com/witoldsz/angular-http-auth, если ваша безопасность поступает с сервера как ошибки http 401.

Ответ 9

Я столкнулся с тем же вопросом. Решил это, используя это.

angular.module('app', ['ui.router']).run(function($rootScope, $state) {
    yourpromise.then(function(resolvedVal){
        $rootScope.$on('$stateChangeStart', function(event){
           if(!resolvedVal.allow){
               event.preventDefault();
               $state.go('unauthState');
           }
        })
    }).catch(function(){
        $rootScope.$on('$stateChangeStart', function(event){
           event.preventDefault();
           $state.go('unauthState');
           //DO Something ELSE
        })

    });

Ответ 10

        var lastTransition = null;
        $rootScope.$on('$stateChangeStart',
            function(event, toState, toParams, fromState, fromParams, options)  {
                // state change listener will keep getting fired while waiting for promise so if detect another call to same transition then just return immediately
                if(lastTransition === toState.name) {
                    return;
                }

                lastTransition = toState.name;

                // Don't do transition until after promise resolved
                event.preventDefault();
                return executeFunctionThatReturnsPromise(fromParams, toParams).then(function(result) {
                    $state.go(toState,toParams,options);
                });
        });

У меня возникли некоторые проблемы с использованием булевского предохранителя для исключения бесконечного цикла во время stateChangeStart, поэтому он использовал этот подход, просто проверяя, был ли повторен один и тот же переход снова, и сразу же возвращается, если так, потому что в этом случае обещание все еще не разрешено.