Remarque
Les réponses qui suggèrent d'utiliser des variantes de $window.history.back()
ont toutes manqué une partie cruciale de la question: Comment restaurer l'état de l'application à l'emplacement d'état correct à mesure que l'historique saute (arrière / avant / actualisation). Dans cet esprit; s'il vous plaît, lisez la suite.
Oui, il est possible d'avoir le navigateur en arrière / en avant (historique) et actualiser tout en exécutant une ui-router
machine à états pure , mais cela prend un peu de travail.
Vous avez besoin de plusieurs composants:
URL uniques . Le navigateur n'active les boutons Précédent / Suivant que lorsque vous modifiez les URL, vous devez donc générer une URL unique par état visité. Cependant, ces URL n'ont pas besoin de contenir d'informations d'état.
Un service de session . Chaque URL générée est corrélée à un état particulier, vous avez donc besoin d'un moyen de stocker vos paires url-état afin de pouvoir récupérer les informations d'état après le redémarrage de votre application angulaire par des clics arrière / avant ou d'actualisation.
Une histoire d'État . Un dictionnaire simple des états de ui-router avec une URL unique. Si vous pouvez compter sur HTML5, vous pouvez utiliser l' API d'Historique HTML5 , mais si, comme moi, vous ne pouvez pas, vous pouvez l'implémenter vous-même en quelques lignes de code (voir ci-dessous).
Un service de localisation . Enfin, vous devez être en mesure de gérer à la fois les changements d'état de l'interface utilisateur, déclenchés en interne par votre code, et les changements d'URL normaux du navigateur généralement déclenchés par l'utilisateur cliquant sur les boutons du navigateur ou en tapant des éléments dans la barre du navigateur. Tout cela peut devenir un peu délicat car il est facile de se demander ce qui a déclenché quoi.
Voici ma mise en œuvre de chacune de ces exigences. J'ai tout regroupé en trois services:
Le service de session
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
Ceci est un wrapper pour l' sessionStorage
objet javascript . Je l'ai réduit pour plus de clarté ici. Pour une explication complète de cela, veuillez consulter: Comment gérer l'actualisation de la page avec une application à une seule page AngularJS
Le service d'histoire de l'État
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
Il StateHistoryService
s'occupe du stockage et de la récupération des états historiques définis par des URL uniques générées. C'est vraiment juste un wrapper pratique pour un objet de style dictionnaire.
Le service de localisation d'État
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
Le StateLocationService
gère deux événements:
locationChange . Ceci est appelé lorsque l'emplacement du navigateur est modifié, généralement lorsque vous appuyez sur le bouton Précédent / Suivant / Actualiser ou lorsque l'application démarre pour la première fois ou lorsque l'utilisateur tape une URL. Si un état pour l'emplacement actuel.url existe dans le, StateHistoryService
il est utilisé pour restaurer l'état via ui-routeur $state.go
.
stateChange . Ceci est appelé lorsque vous déplacez l'état en interne. Le nom et les paramètres de l'état actuel sont stockés dans la StateHistoryService
clé par une URL générée. Cette URL générée peut être tout ce que vous voulez, elle peut ou non identifier l'état d'une manière lisible par l'homme. Dans mon cas, j'utilise un paramètre d'état plus une séquence de chiffres générée aléatoirement dérivée d'un guid (voir le pied de page pour l'extrait du générateur de guid). L'URL générée est affichée dans la barre du navigateur et, surtout, ajoutée à la pile d'historique interne du navigateur à l'aide de @location.url url
. Il ajoute l'URL à la pile d'historique du navigateur qui active les boutons avant / arrière.
Le gros problème avec cette technique est que l'appel @location.url url
de la stateChange
méthode déclenchera l' $locationChangeSuccess
événement et appellera donc la locationChange
méthode. De même, l'appel de @state.go
from locationChange
déclenchera l' $stateChangeSuccess
événement et donc la stateChange
méthode. Cela devient très déroutant et gâche l'historique du navigateur sans fin.
La solution est très simple. Vous pouvez voir le preventCall
tableau utilisé comme pile ( pop
et push
). Chaque fois qu'une des méthodes est appelée, elle empêche que l'autre méthode ne soit appelée une seule fois. Cette technique n'interfère pas avec le déclenchement correct des événements $ et maintient tout droit.
Il ne nous reste plus qu'à appeler les HistoryService
méthodes au moment opportun dans le cycle de vie de la transition d'état. Cela se fait dans la .run
méthode AngularJS Apps , comme ceci:
App.run angulaire
angular
.module 'app', ['ui.router']
.run ($rootScope, stateLocationService) ->
$rootScope.$on '$stateChangeSuccess', (event, toState, toParams) ->
stateLocationService.stateChange()
$rootScope.$on '$locationChangeSuccess', ->
stateLocationService.locationChange()
Générer un Guid
Math.guid = ->
s4 = -> Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1)
"#{s4()}#{s4()}-#{s4()}-#{s4()}-#{s4()}-#{s4()}#{s4()}#{s4()}"
Avec tout cela en place, les boutons avant / arrière et le bouton d'actualisation fonctionnent tous comme prévu.