pare a navegação do roteador angular-ui até que a promise seja resolvida

Eu quero evitar alguma oscilação que acontece quando ocorre o tempo limite de rails, mas angular não sabe até o próximo erro de autorização de um recurso.

O que acontece é que o modelo é renderizado, algumas chamadas de ajax para que os resources aconteçam e, em seguida, somos redirecionados para o planejamento de rails para efetuar login. Eu prefiro fazer um ping para rails em todas as mudanças de estado e se a session de rails tiver expirado, então eu irei redirect imediatamente ANTES que o modelo seja renderizado.

ui-roteador tem resolução que pode ser colocada em todas as rotas, mas isso não parece seco em tudo.

O que eu tenho é isso. Mas a promise não é resolvida até que o estado já esteja em transição.

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

Como posso interromper a transição de estado, antes que o novo modelo seja renderizado, com base no resultado de uma promise?

Eu sei que isso é extremamente tarde para o jogo, mas eu queria lançar minha opinião e discutir o que eu acredito ser uma excelente maneira de “pausar” uma mudança de estado. De acordo com a documentação do angular-ui-roteador, qualquer membro do object “resolver” do estado que é uma promise deve ser resolvido antes que o estado seja concluído. Portanto, minha solução funcional (embora ainda não limpa e aperfeiçoada) é adicionar uma promise ao object de resolução do “toState” em “$ stateChangeStart”:

por exemplo:

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

Isso garantirá que a mudança de estado seja válida para a promise a ser resolvida, o que é feito quando a chamada da API é concluída e todas as decisões baseadas no retorno da API são feitas. Eu usei isso para verificar os status de login no lado do servidor antes de permitir que uma nova página seja navegada. Quando a chamada da API resolve, eu uso “event.preventDefault ()” para interromper a navegação original e, em seguida, direcionar para a página de login (ao redor do bloco inteiro de código com um if.name! = “Login”) ou permitir que o usuário para continuar simplesmente resolvendo a promise adiada em vez de tentar usar bypass booleans e preventDefault ().

Embora eu tenha certeza de que o pôster original há muito tempo descobriu seu problema, eu realmente espero que isso ajude alguém lá fora.

EDITAR

Eu percebi que não queria enganar as pessoas. Aqui está como o código deve ficar se você não tiver certeza se os seus estados têm objects de resolução:

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

EDIT 2

a fim de obter esse trabalho para estados que não têm uma definição de resolução, é necessário include isso no app.config:

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

fazendo if (!toState.resolve) { toState.resolve = {} }; em stateChangeStart parece não funcionar, acho ui-roteador não aceita um dit de resolução depois de ter sido inicializado.

Eu acredito que você está procurando por event.preventDefault()

Nota: Use event.preventDefault () para evitar que a transição aconteça.

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

Embora eu provavelmente usaria resolve em config de estado como @charlietfl sugerido

EDITAR :

então eu tive a chance de usar preventDefault () no evento de mudança de estado, e aqui está o que eu fiz:

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

EDITAR

A documentação mais recente inclui um exemplo de como um usuário deve sync() para continuar depois que o preventDefault foi invocado, mas o exemplo fornecido lá usa o evento $locationChangeSuccess que para mim e os comentadores não funcionam, em vez disso use $stateChangeStart como no exemplo abaixo com um evento atualizado:

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

Aqui está a minha solução para este problema. Funciona bem e está no espírito de algumas das outras respostas aqui. Apenas é limpo um pouco. Estou definindo uma variável personalizada chamada ‘stateChangeBypass’ no escopo raiz para evitar o loop infinito. Eu também estou verificando se o estado é ‘login’ e se assim for, isso é sempre permitido.

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

como $ urlRouter.sync () não funciona com stateChangeStart, aqui está uma alternativa:

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

Para adicionar as respostas existentes aqui, eu tive exatamente o mesmo problema; Estávamos usando um manipulador de events no escopo raiz para ouvir $stateChangeStart para o meu tratamento de permissão. Infelizmente isso teve um efeito colateral desagradável de ocasionalmente causar digests infinitos (não sei por que, o código não foi escrito por mim).

A solução que eu event.preventDefault() , que é bastante deficiente, é sempre impedir a transição com event.preventDefault() , em seguida, determinar se o usuário está logado ou não por meio de uma chamada assíncrona. Depois de verificar isso, use $state.go para fazer a transição para um novo estado. O importante, no entanto, é que você defina a propriedade de notify nas opções em $state.go para false. Isso impedirá que as transições de estado $stateChangeStart outro $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 }); }); 

Isso não é muito desejável, mas é necessário para mim devido ao modo como as permissions neste sistema são carregadas; Eu usei um hasPermission síncrono, as permissions podem não ter sido carregadas no momento da solicitação para a página. 🙁 Talvez pudéssemos perguntar ui-roteador para um método continueTransition no evento?

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

O método on retorna a deregistration function for this listener .

Então, aqui está o que você pode fazer:

 var unbindStateChangeEvent = $scope.$on('$stateChangeStart', function(event, toState, toParams) { event.preventDefault(); waitForSomething(function (everythingIsFine) { if(everythingIsFine) { unbindStateChangeEvent(); $state.go(toState, toParams); } }); }); 

Eu realmente gosto da solução sugerida por TheRyBerg , já que você pode fazer tudo em um só lugar e sem muitos truques estranhos. Eu descobri que existe uma maneira de melhorar ainda mais, para que você não precise do stateChangeBypass no rootscope. A ideia principal é que você deseje ter algo inicializado em seu código antes que seu aplicativo possa ser “executado”. Então, se você apenas lembrar se foi inicializado ou não, você pode fazer assim:

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

Então você pode apenas lembrar que seus dados já estão inicializados no serviço do jeito que você gosta, por exemplo:

 function dataService() { var initialized = false; return { initialize: initialize, isInitialized: isInitialized } function intialize() { return $http.get(...) .success(function(response) { initialized=true; }); } function isInitialized() { return initialized; } }; 

Você pode pegar os parâmetros de transição de $ stateChangeStart e armazená-los em um serviço, depois reiniciar a transição depois de lidar com o login. Você também pode consultar https://github.com/witoldsz/angular-http-auth se a sua segurança vier do servidor como erros HTTP 401.

Eu corri para o mesmo problema Resolvi isso usando isso.

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

Eu tive alguns problemas usando um protetor booleano para evitar loop infinito durante stateChangeStart, então tomei essa abordagem apenas verificando se a mesma transição foi tentada novamente e retornando imediatamente se foi, pois, nesse caso, a promise ainda não foi resolvida.