如何让后退按钮与 AngularJS ui-router 状态机一起使用? [英] How do I get the Back Button to work with an AngularJS ui-router state machine?
问题描述
我已经使用 ui-router 实现了一个 angularjs 单页应用程序.
I have implemented an angularjs single page application using ui-router.
最初我使用不同的 url 来标识每个状态,但这会导致不友好的 GUID 打包 url.
Originally I identified each state using a distinct url however this made for unfriendly, GUID packed urls.
所以我现在将我的网站定义为一个更简单的状态机.状态不是由 url 标识的,而是根据需要简单地转换为,如下所示:
So I have now defined my site as a much simpler state-machine. The states are not identified by urls but are simply transitioned to as required, like this:
定义嵌套状态
angular
.module 'app', ['ui.router']
.config ($stateProvider) ->
$stateProvider
.state 'main',
templateUrl: 'main.html'
controller: 'mainCtrl'
params: ['locationId']
.state 'folder',
templateUrl: 'folder.html'
parent: 'main'
controller: 'folderCtrl'
resolve:
folder:(apiService) -> apiService.get '#base/folder/#locationId'
过渡到定义的状态
#The ui-sref attrib transitions to the 'folder' state
a(ui-sref="folder({locationId:'{{folder.Id}}'})")
| {{ folder.Name }}
这个系统运行良好,我喜欢它简洁的语法.但是,由于我没有使用 url,后退按钮不起作用.
This system works very well and I love its clean syntax. However, as I am not using urls the back button does not work.
如何保持我整洁的 ui-router 状态机但启用后退按钮功能?
How do I keep my neat ui-router state-machine but enable the back button functionality?
推荐答案
注意
建议使用 $window.history.back()
变体的答案都忽略了问题的一个关键部分:如何恢复应用程序的状态到随着历史跳转(后退/前进/刷新)正确的状态位置.考虑到这一点;请继续阅读.
Note
The answers that suggest using variations of $window.history.back()
have all missed a crucial part of the question: How to restore the application's state to the correct state-location as the history jumps (back/forward/refresh). With that in mind; please, read on.
是的,可以在运行纯 ui-router
状态机时让浏览器后退/前进(历史记录)和刷新,但这需要一些操作.
Yes, it is possible to have the browser back/forward (history) and refresh whilst running a pure ui-router
state-machine but it takes a bit of doing.
您需要几个组件:
唯一网址.浏览器仅在您更改 url 时启用后退/前进按钮,因此您必须为每个访问状态生成一个唯一的 url.不过,这些 url 不需要包含任何状态信息.
Unique URLs. The browser only enables the back/forward buttons when you change urls, so you must generate a unique url per visited state. These urls need not contain any state information though.
会话服务.每个生成的 url 都与特定状态相关,因此您需要一种方法来存储 url-state 对,以便您可以在通过后退/前进或刷新点击重新启动 angular 应用程序后检索状态信息.
A Session Service. Each generated url is correlated to a particular state so you need a way to store your url-state pairs so that you can retrieve the state information after your angular app has been restarted by back / forward or refresh clicks.
国家历史.一个由唯一 url 键控的 ui-router 状态的简单字典.如果您可以依赖 HTML5,那么您可以使用 HTML5 History API,但如果像我一样,你不能,那么你可以用几行代码自己实现它(见下文).
A State History. A simple dictionary of ui-router states keyed by unique url. If you can rely on HTML5 then you can use the HTML5 History API, but if, like me, you can't then you can implement it yourself in a few lines of code (see below).
定位服务.最后,您需要能够管理由代码内部触发的 ui-router 状态更改,以及通常由用户单击浏览器按钮或在浏览器栏中输入内容触发的正常浏览器 url 更改.这一切都会变得有点棘手,因为很容易混淆触发了什么.
A Location Service. Finally, you need to be able manage both ui-router state changes, triggered internally by your code, and normal browser url changes typically triggered by the user clicking browser buttons or typing stuff into the browser bar. This can all get a bit tricky because it is easy to get confused about what triggered what.
这是我对这些要求中的每一个的实现.我已将所有内容捆绑到三个服务中:
Here is my implementation of each of these requirements. I have bundled everything up into three services:
会话服务
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
这是 javascript sessionStorage
对象的包装器.为了清楚起见,我在这里将其删减.有关这方面的完整说明,请参阅:如何使用 AngularJS 单页应用程序处理页面刷新
This is a wrapper for the javascript sessionStorage
object. I have cut it down for clarity here. For a full explanation of this please see: How do I handle page refreshing with an AngularJS Single Page Application
国家历史服务
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
负责存储和检索由生成的唯一 url 键控的历史状态.它实际上只是一个字典样式对象的便利包装器.
The StateHistoryService
looks after the storage and retrieval of historical states keyed by generated, unique urls. It is really just a convenience wrapper for a dictionary style object.
州定位服务
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
处理两个事件:
locationChange.这在浏览器位置更改时调用,通常是在按下后退/前进/刷新按钮时或应用程序首次启动时或用户输入 url 时.如果
StateHistoryService
中存在当前 location.url 的状态,那么它用于通过 ui-router 的$state.go
恢复状态.
locationChange. This is called when the browsers location is changed, typically when the back/forward/refresh button is pressed or when the app first starts or when the user types in a url. If a state for the current location.url exists in the
StateHistoryService
then it is used to restore the state via ui-router's$state.go
.
stateChange.当您在内部移动状态时会调用它.当前状态的名称和参数存储在由生成的 url 键控的 StateHistoryService
中.这个生成的 url 可以是你想要的任何东西,它可能会或可能不会以人类可读的方式识别状态.在我的情况下,我使用状态参数加上从 guid 派生的随机生成的数字序列(请参阅 guid 生成器片段的脚).生成的 url 显示在浏览器栏中,至关重要的是,使用 @location.url url url
添加到浏览器的内部历史堆栈中.它将 url 添加到浏览器的历史堆栈中,以启用前进/后退按钮.
stateChange. This is called when you move state internally. The current state's name and params are stored in the StateHistoryService
keyed by a generated url. This generated url can be anything you want, it may or may not identify the state in a human readable way. In my case I am using a state param plus a randomly generated sequence of digits derived from a guid (see foot for the guid generator snippet). The generated url is displayed in the browser bar and, crucially, added to the browser's internal history stack using @location.url url
. Its adding the url to the browser's history stack that enables the forward / back buttons.
这种技术的大问题是在stateChange
方法中调用@location.url url
会触发$locationChangeSuccess
事件等等调用 locationChange
方法.同样从 locationChange
调用 @state.go
将触发 $stateChangeSuccess
事件和 stateChange
方法.这会变得非常混乱,并且无休止地弄乱了浏览器历史记录.
The big problem with this technique is that calling @location.url url
in the stateChange
method will trigger the $locationChangeSuccess
event and so call the locationChange
method. Equally calling the @state.go
from locationChange
will trigger the $stateChangeSuccess
event and so the stateChange
method. This gets very confusing and messes up the browser history no end.
解决方法很简单.您可以看到 preventCall
数组被用作堆栈(pop
和 push
).每次调用其中一个方法时,它都会阻止另一个方法只被调用一次.这种技术不会干扰 $ 事件的正确触发,并保持一切正常.
The solution is very simple. You can see the preventCall
array being used as a stack (pop
and push
). Each time one of the methods is called it prevents the other method being called one-time-only. This technique does not interfere with the correct triggering of the $ events and keeps everything straight.
现在我们需要做的就是在状态转换生命周期中的适当时间调用 HistoryService
方法.这是在 AngularJS Apps .run
方法中完成的,如下所示:
Now all we need to do is call the HistoryService
methods at the appropriate time in the state transition life-cycle. This is done in the AngularJS Apps .run
method, like this:
Angular app.run
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()}"
有了所有这些,前进/后退按钮和刷新按钮都按预期工作.
With all this in place, the forward / back buttons and the refresh button all work as expected.
这篇关于如何让后退按钮与 AngularJS ui-router 状态机一起使用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!