停止angular-ui-router导航直到promise得到解决

Hom*_*man 45 promise angularjs angular-ui-router

我希望防止在rails设置超时时发生的一些闪烁,但是角度在资源的下一个授权错误之前不会知道.

发生的是模板被渲染,一些ajax调用资源发生,然后我们被重定向到rails设计登录.我宁愿在每次状态更改时对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'
          }
        })

    });
Run Code Online (Sandbox Code Playgroud)

在呈现新模板之前,如何根据承诺的结果中断状态转换?

Joe*_*gan 46

我知道这场比赛已经很晚了,但我想把我的意见抛在脑后,讨论一下我认为是"暂停"状态改变的好方法.根据angular-ui-router的文档,必须在状态加载完成之前解析作为promise的状态的"resolve"对象的任何成员.所以我的功能(虽然尚未清理和完善)解决方案是在"$ stateChangeStart"上为"toState"的resolve对象添加一个promise:

例如:

$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;
        }
    ]
});
Run Code Online (Sandbox Code Playgroud)

这将确保状态更改适用于要在API调用完成时完成的承诺以及基于API返回的所有决策.在允许导航新页面之前,我已经使用它来检查服务器端的登录状态.当API调用解析时,我使用"event.preventDefault()"来停止原始导航,然后路由到登录页面(用if state.name!="login"围绕整个代码块)或允许用户继续简单地解决延迟的承诺而不是尝试使用旁路布尔值和preventDefault().

虽然我确定原来的海报早已解决了他们的问题,但我真的希望这有助于其他人.

编辑

我想我不想误导别人.如果您不确定您的状态是否具有解析对象,那么代码应该是什么样子:

$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;
        }
    ]
});
Run Code Online (Sandbox Code Playgroud)

编辑2

为了使这个适用于没有解析定义的状态,您需要在app.config中添加它:

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

            return $delegate.apply(this, arguments);
        };
Run Code Online (Sandbox Code Playgroud)

这样做if (!toState.resolve) { toState.resolve = {} };在stateChangeStart似乎没有工作,我认为它已被初始化后,用户界面路由器不接受决心字典.

  • 你必须为每个州添加一个至少一个`resolve:{}`来实现这个目的.`if(!toState.resolve){toState.resolve = {}};`不起作用 (6认同)
  • 如果toState有其他结果,这似乎对我有用.如果它没有其他解析,我添加的那个就不会被调用. (3认同)
  • 这当然取决于您所有州都有解决对象的事实.我的应用程序是这样设置的.这很容易做到,但是嵌套if/else检查上面代码中的resolve对象也非常简单. (2认同)

Aiv*_*ras 27

我相信你在找 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
})
Run Code Online (Sandbox Code Playgroud)

虽然我可能会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
        }
    }
)

}
Run Code Online (Sandbox Code Playgroud)

编辑

较新的文档包含一个示例,说明sync()preventDefault调用后用户应该如何继续,但是如果使用了$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();
        });
    });
Run Code Online (Sandbox Code Playgroud)

  • @ivarPrudnikov感谢您的更新.但是,你确定$ urlRouter.sync()恢复应用于$ stateChangeStart的event.preventDefault()吗?当然urlRouter工作在位置级别,而不是由ui-router提供的状态.我自己尝试过并经历了一些奇怪的行为,但它没有用. (4认同)
  • 此问题涉及文档的最后一次编辑并未指出它使用$ locationChangeSuccess而不是问题所要求的$ stateChangeStart.前者无法访问toParams和toState,这对于身份验证逻辑至关重要.另外,带有urlRouter.sync()的$ locationChangeSuccess对我来说不起作用,$ locationChangeStart没有,但仍然不使用$ state. (3认同)
  • @CarbonDry我确实更新了最后一次编辑,是的,这是正确的,文档中的示例实际上对我也不起作用,我之前是盲目地复制它.我确实使用`$ stateChangeStart`. (2认同)

小智 24

这是我对这个问题的解决方案.它运作良好,并且在这里有一些其他答案的精神.它只是清理了一点.我正在根范围上设置一个名为'stateChangeBypass'的自定义变量,以防止无限循环.我还要检查状态是否为"登录",如果是,则始终允许.

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');
            }
        });

    });
}
Run Code Online (Sandbox Code Playgroud)


oor*_*ori 15

由于$ 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
        }
    });
Run Code Online (Sandbox Code Playgroud)

  • 为此,您需要将bypass重置为false,否则*next*状态转换也将绕过此验证. (2认同)