笔记
建议使用 的变体的答案$window.history.back()
都错过了问题的一个关键部分:如何在历史跳转(后退/前进/刷新)时将应用程序的状态恢复到正确的状态位置。考虑到这一点; 请继续阅读。
是的,可以在运行纯ui-router
状态机的同时让浏览器后退/前进(历史记录)和刷新,但这需要一些操作。
您需要几个组件:
唯一网址。浏览器仅在您更改 url 时启用后退/前进按钮,因此您必须为每个访问状态生成一个唯一的 url。不过,这些 url 不需要包含任何状态信息。
会话服务。每个生成的 url 都与特定状态相关,因此您需要一种方法来存储 url-state 对,以便您可以在通过后退/前进或刷新点击重新启动 angular 应用程序后检索状态信息。
一个国家历史。一个由唯一 url 键控的 ui-router 状态的简单字典。如果您可以依赖 HTML5,那么您可以使用HTML5 History API,但是如果像我一样不能,那么您可以自己用几行代码来实现它(见下文)。
定位服务。最后,您需要能够管理由代码内部触发的 ui-router 状态更改,以及通常由用户单击浏览器按钮或在浏览器栏中输入内容触发的正常浏览器 url 更改。这一切都会变得有点棘手,因为很容易混淆触发了什么。
这是我对这些要求中的每一个的实现。我已将所有内容捆绑为三项服务:
会话服务
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
这是 javascriptsessionStorage
对象的包装器。为了清楚起见,我在这里将其删减。有关这方面的完整说明,请参阅:如何使用 AngularJS 单页应用程序处理页面刷新
国家历史服务
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
该StateHistoryService
所产生的,唯一的网址键入的历史状态的存储和检索后的外观。它实际上只是字典样式对象的便利包装器。
州定位服务
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
在StateLocationService
处理两个事件:
位置更改。这在浏览器位置更改时调用,通常是在按下后退/前进/刷新按钮时或应用程序首次启动时或用户输入 url 时。如果当前 location.url 的状态存在于 中,StateHistoryService
则它用于通过 ui-router 的 恢复状态$state.go
。
状态变化。当您在内部移动状态时会调用它。当前状态的名称和参数存储在StateHistoryService
生成的 url 中。这个生成的 url 可以是你想要的任何东西,它可能会或可能不会以人类可读的方式识别状态。在我的例子中,我使用了一个 state 参数加上一个从 guid 派生的随机生成的数字序列(参见 foot 以获得 guid 生成器片段)。生成的 url 显示在浏览器栏中,并且至关重要的是,使用@location.url url
. 它将 url 添加到浏览器的历史堆栈中,以启用前进/后退按钮。
这种技术的最大问题是,在调用@location.url url
的stateChange
方法会触发$locationChangeSuccess
事件,那么调用该locationChange
方法。同样调用@state.go
fromlocationChange
将触发$stateChangeSuccess
事件和stateChange
方法。这会变得非常混乱,并且会无休止地弄乱浏览器历史记录。
解决方法很简单。您可以看到preventCall
数组被用作堆栈(pop
和push
)。每次调用其中一个方法时,它都会阻止另一个方法只被调用一次。这种技术不会干扰 $ 事件的正确触发并保持一切正常。
现在我们需要做的就是HistoryService
在状态转换生命周期中的适当时间调用这些方法。这是在 AngularJS Apps.run
方法中完成的,如下所示:
Angular 应用程序运行
angular
.module 'app', ['ui.router']
.run ($rootScope, stateLocationService) ->
$rootScope.$on '$stateChangeSuccess', (event, toState, toParams) ->
stateLocationService.stateChange()
$rootScope.$on '$locationChangeSuccess', ->
stateLocationService.locationChange()
生成指南
Math.guid = ->
s4 = -> Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1)
"#{s4()}#{s4()}-#{s4()}-#{s4()}-#{s4()}-#{s4()}#{s4()}#{s4()}"
有了所有这些,前进/后退按钮和刷新按钮都按预期工作。