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

时间:2013-11-20 11:08:58

标签: angularjs angular-ui-router promise

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

发生的是模板呈现,一些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'
          }
        })

    });

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

10 个答案:

答案 0 :(得分:46)

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

例如:

yyyy

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

虽然我确定原版海报早已解决了他们的问题,但我真的希望这可以帮助其他人。

修改

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

$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;
        }
    ]
});

编辑2

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

$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;
        }
    ]
});

在stateChangeStart中执行 var $delegate = $stateProvider.state; $stateProvider.state = function(name, definition) { if (!definition.resolve) { definition.resolve = {}; } return $delegate.apply(this, arguments); }; 似乎不起作用,我认为ui-router在初始化后不接受解析dict。

答案 1 :(得分: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
})

虽然我可能会在状态配置中使用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
        }
    }
)

}

修改

Newer documentation包含一个示例,说明如何在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();
        });
    });

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

    });
}

答案 3 :(得分: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
        }
    });

答案 4 :(得分:4)

要在这里添加现有答案,我有完全相同的问题;我们在根作用域上使用事件处理程序来监听$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,则在请求页面时可能尚未加载权限。 :(也许我们可以向ui-router询问事件的continueTransition方法?

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

答案 5 :(得分:4)

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

答案 6 :(得分:2)

我非常喜欢TheRyBerg建议的解决方案,因为你可以在一个地方做所有事情而不需要太多奇怪的技巧。我发现有一种方法可以进一步改进它,因此你不需要在rootscope中使用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;
    }
};

答案 7 :(得分:1)

您可以从$ stateChangeStart中获取转换参数并将其存储在服务中,然后在处理完登录后重新启动转换。如果您的安全性来自服务器,您也可以查看https://github.com/witoldsz/angular-http-auth作为http 401错误。

答案 8 :(得分:0)

我遇到了同样的问题使用它来解决它。

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

    });

答案 9 :(得分:0)

        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期间使用布尔保护来避免无限循环时遇到了一些问题,所以采用这种方法只是检查是否再次尝试相同的转换并立即返回(如果是这样),因为在这种情况下,promise仍然没有解决。