Nota
Le risposte che suggeriscono l'utilizzo di variazioni di $window.history.back()
hanno tutte perso una parte cruciale della domanda: come ripristinare lo stato dell'applicazione nella posizione dello stato corretta quando la cronologia salta (indietro / avanti / aggiorna). Con quello in mente; per favore, continua a leggere.
Sì, è possibile fare in modo che il browser avanti / indietro (cronologia) e si aggiorni durante l'esecuzione di una ui-router
macchina a stati puri , ma ci vuole un po 'di tempo.
Hai bisogno di diversi componenti:
URL univoci . Il browser abilita i pulsanti Indietro / Avanti solo quando si modificano gli URL, quindi è necessario generare un URL univoco per stato visitato. Tuttavia, questi URL non devono contenere alcuna informazione di stato.
Un servizio di sessione . Ogni URL generato è correlato a uno stato particolare, quindi è necessario un modo per memorizzare le coppie di stato dell'URL in modo da poter recuperare le informazioni sullo stato dopo che la tua app angolare è stata riavviata con clic indietro / avanti o di aggiornamento.
Una storia di stato . Un semplice dizionario degli stati ui-router con chiave da un URL univoco. Se puoi fare affidamento su HTML5, puoi utilizzare l' API cronologia HTML5 , ma se, come me, non puoi, puoi implementarlo da solo in poche righe di codice (vedi sotto).
Un servizio di localizzazione . Infine, è necessario essere in grado di gestire sia le modifiche allo stato dell'interfaccia utente-router, attivate internamente dal codice, sia le normali modifiche all'URL del browser tipicamente attivate dall'utente che fa clic sui pulsanti del browser o digita elementi nella barra del browser. Tutto questo può diventare un po 'complicato perché è facile confondersi su ciò che ha attivato cosa.
Ecco la mia implementazione di ciascuno di questi requisiti. Ho raggruppato tutto in tre servizi:
Il servizio di sessione
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
Questo è un wrapper per l' sessionStorage
oggetto javascript . L'ho tagliato per chiarezza qui. Per una spiegazione completa di ciò, vedere: Come gestire l'aggiornamento della pagina con un'applicazione AngularJS a pagina singola
Il servizio di storia dello stato
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
Si StateHistoryService
occupa della memorizzazione e del recupero degli stati storici codificati da URL univoci generati. In realtà è solo un comodo wrapper per un oggetto in stile dizionario.
Il servizio di localizzazione statale
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
La StateLocationService
gestisce due eventi:
locationChange . Viene chiamato quando la posizione del browser viene modificata, in genere quando viene premuto il pulsante Indietro / Avanti / Aggiorna o quando l'app viene avviata per la prima volta o quando l'utente digita un URL. Se uno stato per l'attuale location.url esiste in StateHistoryService
allora viene utilizzato per ripristinare lo stato tramite ui-router $state.go
.
stateChange . Questo viene chiamato quando si sposta lo stato internamente. Il nome e i parametri dello stato corrente vengono memorizzati nel StateHistoryService
codice da un URL generato. Questo URL generato può essere qualsiasi cosa tu voglia, può o meno identificare lo stato in modo leggibile dall'uomo. Nel mio caso sto usando un parametro di stato più una sequenza di cifre generata casualmente derivata da un guid (vedi il piede per lo snippet del generatore di guid). L'URL generato viene visualizzato nella barra del browser e, soprattutto, aggiunto allo stack della cronologia interna del browser utilizzando @location.url url
. Aggiunge l'URL allo stack della cronologia del browser che abilita i pulsanti avanti / indietro.
Il grosso problema con questa tecnica è che la chiamata @location.url url
nel stateChange
metodo attiverà l' $locationChangeSuccess
evento e quindi chiamerà il locationChange
metodo. Allo stesso modo chiamando il @state.go
from locationChange
attiverà l' $stateChangeSuccess
evento e quindi il stateChange
metodo. Questo diventa molto confuso e incasina la cronologia del browser senza fine.
La soluzione è molto semplice. Puoi vedere l' preventCall
array utilizzato come stack ( pop
e push
). Ogni volta che viene chiamato uno dei metodi, si impedisce che l'altro metodo venga chiamato una sola volta. Questa tecnica non interferisce con il corretto innesco degli eventi $ e mantiene tutto in ordine.
Ora tutto ciò che dobbiamo fare è chiamare i HistoryService
metodi al momento opportuno nel ciclo di vita della transizione di stato. Questo viene fatto nel .run
metodo AngularJS Apps , in questo modo:
App.run angolare
angular
.module 'app', ['ui.router']
.run ($rootScope, stateLocationService) ->
$rootScope.$on '$stateChangeSuccess', (event, toState, toParams) ->
stateLocationService.stateChange()
$rootScope.$on '$locationChangeSuccess', ->
stateLocationService.locationChange()
Genera una guida
Math.guid = ->
s4 = -> Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1)
"#{s4()}#{s4()}-#{s4()}-#{s4()}-#{s4()}-#{s4()}#{s4()}#{s4()}"
Con tutto ciò a posto, i pulsanti avanti / indietro e il pulsante di aggiornamento funzionano tutti come previsto.