停止angularUI路由器导航,直到承诺解决

我想防止一些闪烁发生时,轨道devise超时发生,但angular度不知道,直到从资源的下一个授权错误。

会发生什么是模板呈现,一些AJAX调用资源发生,然后我们被redirect到轨道deviselogin。 我宁愿做一个ping导轨上的每一个状态的变化,如果rails会话过期,那么我会立即redirect之前,模板呈现。

UI路由器已解决,可以放在每一个路线,但似乎并不DRY。

我拥有的是这个。 但是这个承诺在国家已经过渡之前是不会解决的。

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

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

我知道这场比赛已经非常晚了,但是我想把我的意见放在那里,并且讨论我认为是“暂停”状态改变的极好方法。 根据angular-ui-router的文档,必须在状态完成加载之前parsing作为promise的状态的“resolve”对象的任何成员。 所以我的function(虽然还没有清理和完善)的解决scheme,是增加一个承诺的“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返回所做的所有决策都有效。 我已经使用它来检查服务器端的login状态,然后允许导航到一个新的页面。 当API调用解决时,我使用“event.preventDefault()”来停止原来的导航,然后路由到login页面(用if state.name!=“login”包围整个代码块),或者允许用户继续简单地解决延期承诺,而不是尝试使用旁路布尔和preventDefault()。

虽然我确定原来的海报早就知道了他们的问题,但我真的希望这能帮助别人。

编辑

我想我不想误导别人。 如果您不确定您的状态是否有parsing对象,则代码如下所示:

 $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

为了得到这个没有parsing定义的状态,你需要在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路由器不接受一个parsing字典后,它已被初始化。

我相信你正在寻找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 }) 

虽然我可能会使用状态configuration中的resolve ,如@charlietflbuild议

编辑

所以我有机会在状态改变事件中使用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 } } ) } 

编辑

较新的文档包括一个示例,说明在preventDefault之后用户应该如何继续sync() ,但是已经提供了使用$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(); }); }); 

这是我解决这个问题的方法。 它运作良好,并在这里的一些其他答案的精神。 它只是清理了一下。 我在根作用域上设置一个名为“stateChangeBypass”的自定义variables来防止无限循环。 我也在检查,看看状态是否是“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'); } }); }); } 

因为$ urlRouter.sync()不能和stateChangeStart一起工作,下面是一个替代scheme:

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

为了增加现有的答案,我有完全相同的问题; 我们在根作用域上使用了一个事件处理程序来监听$stateChangeStart进行权限处理。 不幸的是,这有一个偶尔造成无限摘要(不知道为什么,代码不是由我写的)的副作用。

我想到的解决scheme是相当缺乏的, 总是阻止event.preventDefault()的转换,然后通过asynchronous调用来确定用户是否login。 validation完成后,使用$state.go转换到新的状态。 但是,重要的一点是,您将$state.go的选项的notify属性设置为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路由器的事件continueTransition方法?

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

on方法a deregistration function for this listener返回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); } }); }); 

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

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

我遇到了同样的问题解决了这个问题。

 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 }) }); 
  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期间的无限循环,所以采取了这种方法,只是检查是否再次尝试相同的转换,如果是这样,立即返回,因为在这种情况下,承诺还没有解决。