Nota
Las respuestas que sugieren el uso de variaciones de $window.history.back()
han pasado por alto una parte crucial de la pregunta: cómo restaurar el estado de la aplicación a la ubicación de estado correcta a medida que el historial salta (atrás / adelante / actualizar). Con eso en mente; por favor, sigue leyendo.
Sí, es posible hacer retroceder / avanzar el navegador (historial) y actualizar mientras se ejecuta una ui-router
máquina de estado puro , pero requiere un poco de trabajo.
Necesita varios componentes:
URL únicas . El navegador solo habilita los botones de retroceso / avance cuando cambia las URL, por lo que debe generar una URL única por estado visitado. Sin embargo, estas URL no necesitan contener ninguna información de estado.
Un servicio de sesión . Cada URL generada se correlaciona con un estado particular, por lo que necesita una forma de almacenar sus pares de URL-estado para que pueda recuperar la información del estado después de que su aplicación angular se haya reiniciado mediante retroceso / avance o clics de actualización.
Una historia del estado . Un diccionario simple de estados de ui-router codificados por una URL única. Si puede confiar en HTML5, puede usar la API de historial de HTML5 , pero si, como yo, no puede, puede implementarla usted mismo en unas pocas líneas de código (ver más abajo).
Un servicio de localización . Finalmente, debe poder administrar los cambios de estado de ui-router, activados internamente por su código, y los cambios normales de la URL del navegador que normalmente se activan cuando el usuario hace clic en los botones del navegador o escribe cosas en la barra del navegador. Todo esto puede volverse un poco complicado porque es fácil confundirse acerca de qué desencadenó qué.
Aquí está mi implementación de cada uno de estos requisitos. He agrupado todo en tres servicios:
El servicio de sesión
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
accessor:(name, value)->
return @getStorage name unless value?
@setStorage name, value
angular
.module 'app.Services'
.service 'sessionService', SessionService
Este es un contenedor para el sessionStorage
objeto javascript . Lo he cortado para mayor claridad aquí. Para obtener una explicación completa de esto, consulte: ¿Cómo manejo la actualización de la página con una aplicación de página única AngularJS?
El Servicio de Historia del 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
Se StateHistoryService
ocupa del almacenamiento y recuperación de estados históricos codificados por URL únicas generadas. En realidad, es solo un contenedor de conveniencia para un objeto de estilo de diccionario.
El servicio de ubicación estatal
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}
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
El StateLocationService
maneja dos eventos:
locationChange . Esto se llama cuando se cambia la ubicación del navegador, generalmente cuando se presiona el botón Atrás / Adelante / Actualizar o cuando la aplicación se inicia por primera vez o cuando el usuario escribe una URL. Si existe un estado para la location.url actual en el, StateHistoryService
entonces se usa para restaurar el estado a través de ui-router $state.go
.
stateChange . Esto se llama cuando mueve el estado internamente. El nombre y los parámetros del estado actual se almacenan en la StateHistoryService
clave mediante una URL generada. Esta URL generada puede ser cualquier cosa que desee, puede o no identificar el estado de una manera legible por humanos. En mi caso, estoy usando un parámetro de estado más una secuencia de dígitos generada aleatoriamente derivada de un guid (vea el pie para el fragmento del generador de guid). La URL generada se muestra en la barra del navegador y, lo que es más importante, se agrega a la pila de historial interno del navegador mediante @location.url url
. Es agregar la URL a la pila del historial del navegador que habilita los botones de avance / retroceso.
El gran problema con esta técnica es que llamar @location.url url
al stateChange
método activará el $locationChangeSuccess
evento y, por tanto, llamará al locationChange
método. Igualmente, llamar al @state.go
from locationChange
activará el $stateChangeSuccess
evento y, por lo tanto, el stateChange
método. Esto se vuelve muy confuso y estropea el historial del navegador sin fin.
La solución es muy simple. Puede ver que la preventCall
matriz se usa como una pila ( pop
y push
). Cada vez que se llama a uno de los métodos, se evita que el otro método se llame una sola vez. Esta técnica no interfiere con la activación correcta de los eventos $ y mantiene todo en orden.
Ahora todo lo que tenemos que hacer es llamar a los HistoryService
métodos en el momento apropiado en el ciclo de vida de la transición de estado. Esto se hace en el .run
método de AngularJS Apps , así:
Ejecución de aplicación angular
angular
.module 'app', ['ui.router']
.run ($rootScope, stateLocationService) ->
$rootScope.$on '$stateChangeSuccess', (event, toState, toParams) ->
stateLocationService.stateChange()
$rootScope.$on '$locationChangeSuccess', ->
stateLocationService.locationChange()
Generar una guía
Math.guid = ->
s4 = -> Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1)
"#{s4()}#{s4()}-#{s4()}-#{s4()}-#{s4()}-#{s4()}#{s4()}#{s4()}"
Con todo esto en su lugar, los botones de avance / retroceso y el botón de actualización funcionan como se esperaba.