Nota
Todas as respostas que sugerem o uso de variações de $window.history.back()
perderam uma parte crucial da questão: como restaurar o estado do aplicativo para o local de estado correto conforme o histórico pula (voltar / avançar / atualizar). Com aquilo em mente; por favor, continue a ler.
Sim, é possível retroceder / avançar (histórico) e atualizar o navegador durante a execução de uma ui-router
máquina de estado puro , mas isso demora um pouco.
Você precisa de vários componentes:
URLs exclusivos . O navegador só ativa os botões voltar / avançar quando você altera os urls, portanto, você deve gerar um url exclusivo para cada estado visitado. No entanto, esses urls não precisam conter nenhuma informação de estado.
Um serviço de sessão . Cada url gerado é correlacionado a um estado específico, portanto, você precisa de uma maneira de armazenar seus pares de url-estado para que possa recuperar as informações de estado depois que seu aplicativo angular foi reiniciado por retroceder / avançar ou cliques de atualização.
Uma História do Estado . Um dicionário simples de estados do roteador da interface do usuário codificados por url exclusivo. Se você pode confiar no HTML5, então você pode usar a API HTML5 History , mas se, como eu, você não pode, então você pode implementá-lo em algumas linhas de código (veja abaixo).
Um serviço de localização . Finalmente, você precisa ser capaz de gerenciar as alterações de estado do roteador da interface do usuário, acionadas internamente por seu código, e as alterações normais de URL do navegador, normalmente acionadas pelo usuário clicando nos botões do navegador ou digitando coisas na barra do navegador. Isso tudo pode ser um pouco complicado porque é fácil ficar confuso sobre o que acionou o quê.
Aqui está minha implementação de cada um desses requisitos. Eu agrupei tudo em três serviços:
O Serviço de Sessão
class SessionService
setStorage:(key, value) ->
json = if value is undefined then null else JSON.stringify value
sessionStorage.setItem key, json
getStorage:(key)->
JSON.parse sessionStorage.getItem key
clear: ->
@setStorage(key, null) for key of sessionStorage
stateHistory:(value=null) ->
@accessor 'stateHistory', value
# other properties goes here
accessor:(name, value)->
return @getStorage name unless value?
@setStorage name, value
angular
.module 'app.Services'
.service 'sessionService', SessionService
Este é um wrapper para o sessionStorage
objeto javascript . Eu o cortei para maior clareza aqui. Para obter uma explicação completa sobre isso, consulte: Como faço para atualizar a página com um aplicativo de página única AngularJS
O Serviço de História do Estado
class StateHistoryService
@$inject:['sessionService']
constructor:(@sessionService) ->
set:(key, state)->
history = @sessionService.stateHistory() ? {}
history[key] = state
@sessionService.stateHistory history
get:(key)->
@sessionService.stateHistory()?[key]
angular
.module 'app.Services'
.service 'stateHistoryService', StateHistoryService
O StateHistoryService
cuidado com o armazenamento e recuperação de estados históricos digitados por urls únicas geradas. Na verdade, é apenas um invólucro prático para um objeto de estilo de dicionário.
O Serviço de Localização Estadual
class StateLocationService
preventCall:[]
@$inject:['$location','$state', 'stateHistoryService']
constructor:(@location, @state, @stateHistoryService) ->
locationChange: ->
return if @preventCall.pop('locationChange')?
entry = @stateHistoryService.get @location.url()
return unless entry?
@preventCall.push 'stateChange'
@state.go entry.name, entry.params, {location:false}
stateChange: ->
return if @preventCall.pop('stateChange')?
entry = {name: @state.current.name, params: @state.params}
#generate your site specific, unique url here
url = "/#{@state.params.subscriptionUrl}/#{Math.guid().substr(0,8)}"
@stateHistoryService.set url, entry
@preventCall.push 'locationChange'
@location.url url
angular
.module 'app.Services'
.service 'stateLocationService', StateLocationService
O StateLocationService
trata de dois eventos:
locationChange . Isso é chamado quando a localização do navegador é alterada, normalmente quando o botão voltar / avançar / atualizar é pressionado ou quando o aplicativo é iniciado pela primeira vez ou quando o usuário digita um url. Se um estado para o location.url atual existir no StateHistoryService
, ele será usado para restaurar o estado por meio do ui-roteador $state.go
.
stateChange . Isso é chamado quando você move o estado internamente. O nome e os parâmetros do estado atual são armazenados no StateHistoryService
digitado por um url gerado. Este url gerado pode ser o que você quiser, ele pode ou não identificar o estado de uma forma legível por humanos. No meu caso, estou usando um parâmetro de estado mais uma sequência de dígitos gerada aleatoriamente, derivada de um guid (consulte o pé para o trecho do gerador de guid). O url gerado é exibido na barra do navegador e, principalmente, adicionado à pilha de histórico interno do navegador usando @location.url url
. É adicionar o url à pilha de histórico do navegador que habilita os botões avançar / voltar.
O grande problema com essa técnica é que chamar @location.url url
o stateChange
método irá disparar o $locationChangeSuccess
evento e então chamar o locationChange
método. Igualmente, chamar o @state.go
from locationChange
irá disparar o $stateChangeSuccess
evento e, portanto, o stateChange
método. Isso fica muito confuso e bagunça o histórico do navegador sem fim.
A solução é muito simples. Você pode ver a preventCall
matriz sendo usada como uma pilha ( pop
e push
). Cada vez que um dos métodos é chamado, ele evita que o outro método seja chamado apenas uma vez. Essa técnica não interfere no disparo correto dos $ events e mantém tudo correto.
Agora tudo o que precisamos fazer é chamar os HistoryService
métodos no momento apropriado no ciclo de vida de transição de estado. Isso é feito no .run
método AngularJS Apps , como este:
Angular app.run
angular
.module 'app', ['ui.router']
.run ($rootScope, stateLocationService) ->
$rootScope.$on '$stateChangeSuccess', (event, toState, toParams) ->
stateLocationService.stateChange()
$rootScope.$on '$locationChangeSuccess', ->
stateLocationService.locationChange()
Gerar um Guid
Math.guid = ->
s4 = -> Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1)
"#{s4()}#{s4()}-#{s4()}-#{s4()}-#{s4()}-#{s4()}#{s4()}#{s4()}"
Com tudo isso no lugar, os botões avançar / voltar e o botão Atualizar funcionam como esperado.