我已经使用ui-router实现了angularjs单页应用程序。
最初,我使用不同的URL来标识每个状态,但这是针对不友好的GUID打包的URL。
因此,我现在将站点定义为一个更简单的状态机。状态不是由url标识的,而是仅根据需要转换为,例如:
定义嵌套状态
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 }}
该系统运行良好,我喜欢它的简洁语法。但是,由于我没有使用网址,因此后退按钮不起作用。
如何保持整洁的ui-router状态机但启用后退按钮功能?
建议使用的变体的答案$window.history.back()都忽略了问题的关键部分:如何随着历史记录的跳转(后退/前进/刷新) 将应用程序的状态恢复 到正确的状态位置。考虑到这一点; 请继续阅读。
$window.history.back()
是的,可以在运行纯ui-router状态机时使浏览器前进/后退(历史记录)并刷新,但是这需要花些时间。
ui-router
您需要几个组件:
唯一网址 。当您更改URL时,浏览器仅启用后退/前进按钮,因此您必须为每个访问状态生成唯一的URL。这些网址虽然不必包含任何状态信息。
会话服务 。每个生成的url都与特定状态相关联,因此您需要一种存储url-state对的方法,以便在通过后退/前进或刷新单击重新启动角度应用后,您可以检索状态信息。
国家历史 。简单的ui-router状态字典,由唯一的url键输入。如果可以依靠HTML5,则可以使用HTML5历史记录API,但如果像我一样不能这样做,则可以自己用几行代码实现它(请参见下文)。
定位服务 。最后,您需要能够管理由代码内部触发的ui路由器状态更改,以及通常由用户单击浏览器按钮或在浏览器栏中键入内容而触发的正常浏览器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对象的包装。为了清楚起见,我将其缩减。有关此内容的完整说明
sessionStorage
国家历史服务处
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所产生的,唯一的网址键入的历史状态的存储和检索后的外观。实际上,这只是字典样式对象的便利包装。
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处理两个事件:
StateLocationService
locationChange 。当更改浏览器的位置时,通常是在按下后退/前进/刷新按钮时,或者在应用首次启动时,或者在用户键入URL时,将调用此方法。如果中存在当前location.url的状态,StateHistoryService则可使用它通过ui-router的恢复状态$state.go。
$state.go
stateChange 。当您在内部移动状态时会调用此方法。当前状态的名称和参数存储在StateHistoryService生成的url键控中。生成的url可以是您想要的任何内容,它可以或可以不以人类可读的方式标识状态。在我的情况下,我使用的是状态参数,以及从GUID派生的随机生成的数字序列(有关GUID生成器代码段,请参见脚)。生成的url显示在浏览器栏中,并且至关重要的是,使用添加到浏览器的内部历史记录堆栈中@location.url url。它将URL添加到浏览器的历史记录堆栈中,从而启用前进/后退按钮。
@location.url url
这种技术的最大问题是,在调用@location.url url的stateChange方法会触发$locationChangeSuccess事件,那么调用该locationChange方法。同样地调用@state.gofrom locationChange将触发$stateChangeSuccess事件以及stateChange方法。这变得非常混乱,并弄乱了浏览器的历史记录。
stateChange
$locationChangeSuccess
locationChange
@state.go
$stateChangeSuccess
解决方案非常简单。您可以看到该preventCall数组被用作堆栈(pop和push)。每次调用其中一个方法时,都会阻止另一个方法仅被一次性调用。此技术不会干扰$事件的正确触发,并使一切保持直线。
preventCall
pop
push
现在,我们要做的就是HistoryService在状态转换生命周期中的适当时间调用方法。这是在AngularJS Apps .run方法中完成的,如下所示:
HistoryService
.run
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()}"
完成所有这些操作后,前进/后退按钮和刷新按钮均可按预期工作。