如何让后退按钮与AngularJS ui-router状态机一起使用?

bio*_*tal 85 javascript coffeescript angularjs angular-ui-router

我已经使用ui-router实现了angularjs单页面应用程序.

最初我使用一个不同的URL来识别每个州,但这是为了不友好的GUID打包网址.

所以我现在将我的网站定义为一个更简单的状态机.状态不是由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'
Run Code Online (Sandbox Code Playgroud)

过渡到定义的国家

#The ui-sref attrib transitions to the 'folder' state

a(ui-sref="folder({locationId:'{{folder.Id}}'})")
    | {{ folder.Name }}
Run Code Online (Sandbox Code Playgroud)

这个系统工作得很好,我喜欢它干净的语法.但是,由于我没有使用URL,后退按钮不起作用.

如何保持我的整洁的ui-router状态机,但启用后退按钮功能?

bio*_*tal 77

注意

建议使用变体的答案$window.history.back()都错过了问题的关键部分:如何在历史跳转(后退/前进/刷新)时将应用程序的状态恢复到正确的状态位置.考虑到这一点; 请继续阅读.


是的,可以让浏览器后退/前进(历史记录)并在运行纯ui-router状态机时刷新,但需要做一些事情.

你需要几个组件:

  • 唯一的网址.浏览器仅在您更改网址时启用后退/前进按钮,因此您必须为每个访问状态生成唯一网址.这些网址不需要包含任何状态信息.

  • 会话服务.每个生成的URL都与特定状态相关联,因此您需要一种方法来存储您的URL状态对,以便在通过后退/前进或刷新点击重新启动角度应用程序后检索状态信息.

  • 国家历史.由唯一网址键入的ui-router状态的简单字典.如果您可以依赖HTML5,那么您可以使用HTML5历史记录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
Run Code Online (Sandbox Code Playgroud)

这是javascript sessionStorage对象的包装器.为了清楚起见,我已将其剪下来了.有关此内容的完整说明,请参阅:如何使用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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

StateLocationService处理两个事件:

  • locationChange.当浏览器位置发生变化时,通常在按下后退/前进/刷新按钮或应用程序首次启动时或用户键入URL时调用此方法.如果当前location.url的状态存在于StateHistoryService那时它用于通过ui-router的状态恢复状态$state.go.

  • stateChange.在内部移动状态时调用此方法.当前状态的名称和参数存储在StateHistoryService由生成的URL键入的内容中.这个生成的url可以是你想要的任何东西,它可能会或可能不会以人类可读的方式识别状态.在我的情况下,我使用状态参数加上从guid派生的随机生成的数字序列(请参阅guid生成器片段的脚).生成的URL显示在浏览器栏中,并且最重要的是,使用添加到浏览器的内部历史堆栈中@location.url url.它将url添加到浏览器的历史堆栈中,以启用前进/后退按钮.

这种技术的最大问题是,在调用@location.url urlstateChange方法会触发$locationChangeSuccess事件,那么调用该locationChange方法.同样调用@state.gofrom locationChange将触发$stateChangeSuccess事件,因此该stateChange方法.这变得非常令人困惑,并且使浏览器历史无缘无故.

解决方案非常简单.您可以看到该preventCall数组被用作堆栈(poppush).每次调用其中一个方法时,它都会阻止另一个方法被一次性调用.这种技术不会干扰$事件的正确触发并保持一切顺利.

现在我们需要做的就是HistoryService在状态转换生命周期中的适当时间调用方法.这是在AngularJS Apps .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()
Run Code Online (Sandbox Code Playgroud)

生成一个Guid

Math.guid = ->
    s4 = -> Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1)
    "#{s4()}#{s4()}-#{s4()}-#{s4()}-#{s4()}-#{s4()}#{s4()}#{s4()}"
Run Code Online (Sandbox Code Playgroud)

完成所有这些后,前进/后退按钮和刷新按钮都按预期工作.

  • 本示例使用哪种语言。我似乎不熟悉。 (3认同)
  • @jlguenego - 建议使用`$ window.history.back()`变体的答案错过了问题的关键部分.关键是在历史记录跳转(后退/前进/刷新)时将应用程序的_state_恢复到正确的状态位置.这通常通过URI提供状态数据来实现.问题是如何在状态位置_without_(显式)URI状态数据之间跳转.鉴于此约束,仅复制后退按钮是不够的,因为这依赖于URI状态数据来重新建立状态位置. (3认同)

Gui*_*ssé 46

app.run(['$window', '$rootScope', 
function ($window ,  $rootScope) {
  $rootScope.goBack = function(){
    $window.history.back();
  }
}]);

<a href="#" ng-click="goBack()">Back</a>
Run Code Online (Sandbox Code Playgroud)

  • 喜欢这个!...大声笑......为了清晰起见,`$ window.history.back`正在进行魔术而不是`$ rootScope` ...如果你愿意,可以回到你的导航栏指令范围. (2认同)

bAr*_*xas 23

在测试了不同的提案后,我发现最简单的方法往往是最好的.

如果你使用角度ui-router并且你需要一个按钮才能最好的回归:

<button onclick="history.back()">Back</button>
Run Code Online (Sandbox Code Playgroud)

要么

<a onclick="history.back()>Back</a>
Run Code Online (Sandbox Code Playgroud)

//警告不设置href或路径将被破坏.

说明:假设一个标准的管理应用程序.搜索对象 - >查看对象 - >编辑对象

使用角度解决方案 从这个状态:

搜索 - >查看 - >编辑

至 :

搜索 - >查看

那就是我们想要的,除非现在你点击浏览器后退按钮,你将再次出现在那里:

搜索 - >查看 - >编辑

这不符合逻辑

但是使用简单的解决方案

<a onclick="history.back()"> Back </a>
Run Code Online (Sandbox Code Playgroud)

来自:

搜索 - >查看 - >编辑

点击按钮后:

搜索 - >查看

点击浏览器后退按钮后:

搜索

一致性得到尊重.:-)


小智 7

如果您正在寻找最简单的"后退"按钮,那么您可以设置如下指令:

    .directive('back', function factory($window) {
      return {
        restrict   : 'E',
        replace    : true,
        transclude : true,
        templateUrl: 'wherever your template is located',
        link: function (scope, element, attrs) {
          scope.navBack = function() {
            $window.history.back();
          };
        }
      };
    });
Run Code Online (Sandbox Code Playgroud)

请记住,这是一个相当愚蠢的"后退"按钮,因为它使用的是浏览器的历史记录.如果您将其包含在目标网页上,它会将用户发回给您登陆之前的任何网址.