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

时间:2022-01-28 12:56:12

I have implemented an angularjs single page application using ui-router.

我使用ui-router实现了一个angularjs单页应用程序。

Originally I identified each state using a distinct url however this made for unfriendly, GUID packed urls.

最初,我使用一个不同的url来标识每个状态,但是这对不友好的、GUID填充的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:

所以我现在将我的站点定义为一个更简单的状态机。这些状态不是由url标识的,而是根据需要简单地转换为:

Define Nested States

定义嵌套状态

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'

Transition to a Defined State

过渡到定义状态

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

a(ui-sref="folder({locationId:'{{folder.Id}}'})")
    | {{ folder.Name }}

This system works very well and I love its clean syntax. However, as I am not using urls the back button does not work.

这个系统工作得很好,我喜欢它简洁的语法。但是,因为我没有使用url,所以后退按钮不能工作。

How do I keep my neat ui-router state-machine but enable the back button functionality?

如何保持整洁的ui-router状态机,同时启用back按钮功能?

8 个解决方案

#1


71  

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.

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


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.

是的,在运行一个纯ui-路由器状态机时,可以让浏览器后退/前进(历史)和刷新,但这需要一点时间。

You need several components:

你需要几个组件:

  • 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时启用后退/前进按钮,因此必须根据访问的状态生成唯一的url。这些url不需要包含任何状态信息。

  • 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都与特定的状态相关,所以您需要一种方法来存储url状态对,以便您可以在通过后退/前进或刷新单击重新启动您的角化应用程序之后检索状态信息。

  • 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键控状态。如果您可以依赖HTML5,那么您可以使用HTML5 History API,但是如果您不能像我一样,那么您可以自己用几行代码实现它(参见下面)。

  • 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.

    位置服务。最后,您需要能够管理ui-router状态的更改(由代码在内部触发)和普通浏览器url更改(通常由用户单击浏览器按钮或在浏览器栏中输入内容触发)。这一切都可能有点棘手,因为很容易混淆什么触发了什么。

Here is my implementation of each of these requirements. I have bundled everything up into three services:

下面是我对每个需求的实现。我把所有的东西都打包成三种服务:

The Session Service

会话服务

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

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

这是javascript sessionStorage对象的包装器。为了清楚起见,我把它剪了下来。对此的完整解释请参见:如何使用AngularJS单页应用程序处理页面刷新

The State History Service

国家历史服务

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

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.

StateHistoryService负责存储和检索由生成的唯一url键控的历史状态。它实际上只是字典样式对象的方便包装。

The State Location Service

该州位置服务

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

The StateLocationService handles two events:

StateLocationService处理两个事件:

  • 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.

    locationChange。当浏览器位置发生改变时,通常在按下back/forward/refresh按钮时,或应用程序首次启动时,或用户在url中键入时调用。如果当前位置的状态。url存在于StateHistoryService中,然后通过ui-router的$state.go来恢复状态。

  • 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。当您在内部移动状态时,这将被调用。当前状态的名称和参数存储在由生成的url键控的StateHistoryService中。生成的url可以是您想要的任何内容,它可以以人类可读的方式标识状态,也可以不标识状态。在我的示例中,我使用状态参数加上从guid派生的随机生成的数字序列(guid生成器片段请参阅foot)。生成的url显示在浏览器栏中,最重要的是,使用@location将其添加到浏览器的内部历史堆栈中。url的url。它将url添加到浏览器的历史堆栈中,以支持向前/后退按钮。

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.

这种技术的最大问题是调用@location。stateChange方法中的url url将触发$locationChangeSuccess事件,因此调用locationChange方法。同样调用@state。从locationChange触发$stateChangeSuccess事件,即stateChange方法。这让浏览器的历史很混乱。

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.

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

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:

现在我们需要做的就是在状态转换生命周期的适当时间调用HistoryService方法。这是在AngularJS应用程序中完成的。run方法如下:

Angular app.run

角app.run

angular
.module 'app', ['ui.router']
.run ($rootScope, stateLocationService) ->

    $rootScope.$on '$stateChangeSuccess', (event, toState, toParams) ->
        stateLocationService.stateChange()

    $rootScope.$on '$locationChangeSuccess', ->
        stateLocationService.locationChange()

Generate a Guid

生成一个Guid

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.

所有这些就绪后,forward / back按钮和refresh按钮都可以正常工作。

#2


46  

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

<a href="#" ng-click="goBack()">Back</a>

#3


21  

After testing different proposals, I found that the easiest way is often the best.

在测试了不同的建议之后,我发现最简单的方法往往是最好的。

If you use angular ui-router and that you need a button to go back best is this:

如果你使用角ui-router而且你需要一个按钮来返回最好的是:

<button onclick="history.back()">Back</button>

or

<a onclick="history.back()>Back</a>

// Warning don't set the href or the path will be broken.

//警告不要设置href,否则路径将被破坏。

Explanation: Suppose a standard management application. Search object -> View object -> Edit object

说明:假设有一个标准的管理应用程序。搜索对象->视图对象->编辑对象

Using the angular solutions From this state :

利用这个状态下的角解:

Search -> View -> Edit

搜索->视图->编辑

To :

:

Search -> View

搜索- >查看

Well that's what we wanted except if now you click the browser back button you'll be there again :

这就是我们想要的但是如果你点击浏览器后退按钮你会再次出现:

Search -> View -> Edit

搜索->视图->编辑

And that is not logical

这是不合逻辑的

However using the simple solution

但是使用简单的解决方案

<a onclick="history.back()"> Back </a>

from :

来自:

Search -> View -> Edit

搜索->视图->编辑

after click on button :

点击按钮后:

Search -> View

搜索- >查看

after click on browser back button :

点击浏览器后退按钮后:

Search

搜索

Consistency is respected. :-)

一致性是尊重。:-)

#4


7  

If you are looking for the simplest "back" button, then you could set up a directive like so:

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

    .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();
          };
        }
      };
    });

Keep in mind this is a fairly unintelligent "back" button because it is using the browser's history. If you include it on your landing page, it will send a user back to any url they came from prior to landing on yours.

记住,这是一个相当不智能的“后退”按钮,因为它使用的是浏览器的历史记录。如果你将它包含在你的登陆页面上,它会将用户发送回他们在登陆之前来自的任何url。

#5


3  

browser's back/forward button solution
I encountered the same problem and I solved it using the popstate event from the $window object and ui-router's $state object. A popstate event is dispatched to the window every time the active history entry changes.
The $stateChangeSuccess and $locationChangeSuccess events are not triggered on browser's button click even though the address bar indicates the new location.
So, assuming you've navigated from states main to folder to main again, when you hit back on the browser, you should be back to the folder route. The path is updated but the view is not and still displays whatever you have on main. try this:

浏览器的后退/前进按钮解决方案遇到了相同的问题,我使用$window对象的popstate事件和ui-router的$state对象解决了这个问题。每当活动历史记录条目发生更改时,将向窗口发送popstate事件。$stateChangeSuccess和$locationChangeSuccess事件不会在浏览器的按钮单击中触发,即使地址栏显示了新的位置。所以,假设你已经从主文件夹导航到主文件夹,当你在浏览器上点击时,你应该回到文件夹路径。路径被更新了,但是视图没有并且仍然显示主界面上的所有内容。试试这个:

angular
.module 'app', ['ui.router']
.run($state, $window) {

     $window.onpopstate = function(event) {

        var stateName = $state.current.name,
            pathname = $window.location.pathname.split('/')[1],
            routeParams = {};  // i.e.- $state.params

        console.log($state.current.name, pathname); // 'main', 'folder'

        if ($state.current.name.indexOf(pathname) === -1) {
            // Optionally set option.notify to false if you don't want 
            // to retrigger another $stateChangeStart event
            $state.go(
              $state.current.name, 
              routeParams,
              {reload:true, notify: false}
            );
        }
    };
}

back/forward buttons should work smoothly after that.

后退/前进按钮在那之后应该能正常工作。

note: check browser compatibility for window.onpopstate() to be sure

注意:请检查windows .onpopstate()的浏览器兼容性以确定

#6


3  

Can be solved using a simple directive "go-back-history", this one is also closing window in case of no previous history.

可以使用一个简单的指令“返回历史”来解决,如果没有以前的历史,这个窗口也是关闭的。

Directive usage

指令的用法

<a data-go-back-history>Previous State</a>

Angular directive declaration

角指令声明

.directive('goBackHistory', ['$window', function ($window) {
    return {
        restrict: 'A',
        link: function (scope, elm, attrs) {
            elm.on('click', function ($event) {
                $event.stopPropagation();
                if ($window.history.length) {
                    $window.history.back();
                } else {
                    $window.close();  
                }
            });
        }
    };
}])

Note: Working using ui-router or not.

注意:是否使用ui-router。

#7


0  

The Back button wasn't working for me as well, but I figured out that the problem was that I had html content inside my main page, in the ui-view element.

Back按钮对我也不起作用,但我发现问题是我的主页中有html内容,在ui-view元素中。

i.e.

即。

<div ui-view>
     <h1> Hey Kids! </h1>
     <!-- More content -->
</div>

So I moved the content into a new .html file, and marked it as a template in the .js file with the routes.

因此,我将内容移动到一个新的.html文件中,并将其标记为.js文件中带有路由的模板。

i.e.

即。

   .state("parent.mystuff", {
        url: "/mystuff",
        controller: 'myStuffCtrl',
        templateUrl: "myStuff.html"
    })

#8


-1  

history.back() and switch to previous state often give effect not that you want. For example, if you have form with tabs and each tab has own state, this just switched previous tab selected, not return from form. In case nested states, you usually need so think about witch of parent states you want to rollback.

back()和切换到以前的状态通常不会产生您想要的效果。例如,如果您有带有制表符的表单,并且每个制表符都有自己的状态,那么这只是切换了之前选中的制表符,而不是从表单返回。如果是嵌套状态,通常需要考虑要回滚的父状态。

This directive solves problem

这个指令可以解决问题

angular.module('app', ['ui-router-back'])

<span ui-back='defaultState'> Go back </span>

It returns to state, that was active before button has displayed. Optional defaultState is state name that used when no previous state in memory. Also it restores scroll position

它返回到状态,该状态在按钮显示之前是活动的。可选的defaultState是在内存中没有先前状态时使用的状态名。它也恢复滚动位置

Code

代码

class UiBackData {
    fromStateName: string;
    fromParams: any;
    fromStateScroll: number;
}

interface IRootScope1 extends ng.IScope {
    uiBackData: UiBackData;
}

class UiBackDirective implements ng.IDirective {
    uiBackDataSave: UiBackData;

    constructor(private $state: angular.ui.IStateService,
        private $rootScope: IRootScope1,
        private $timeout: ng.ITimeoutService) {
    }

    link: ng.IDirectiveLinkFn = (scope, element, attrs) => {
        this.uiBackDataSave = angular.copy(this.$rootScope.uiBackData);

        function parseStateRef(ref, current) {
            var preparsed = ref.match(/^\s*({[^}]*})\s*$/), parsed;
            if (preparsed) ref = current + '(' + preparsed[1] + ')';
            parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/);
            if (!parsed || parsed.length !== 4)
                throw new Error("Invalid state ref '" + ref + "'");
            let paramExpr = parsed[3] || null;
            let copy = angular.copy(scope.$eval(paramExpr));
            return { state: parsed[1], paramExpr: copy };
        }

        element.on('click', (e) => {
            e.preventDefault();

            if (this.uiBackDataSave.fromStateName)
                this.$state.go(this.uiBackDataSave.fromStateName, this.uiBackDataSave.fromParams)
                    .then(state => {
                        // Override ui-router autoscroll 
                        this.$timeout(() => {
                            $(window).scrollTop(this.uiBackDataSave.fromStateScroll);
                        }, 500, false);
                    });
            else {
                var r = parseStateRef((<any>attrs).uiBack, this.$state.current);
                this.$state.go(r.state, r.paramExpr);
            }
        });
    };

    public static factory(): ng.IDirectiveFactory {
        const directive = ($state, $rootScope, $timeout) =>
            new UiBackDirective($state, $rootScope, $timeout);
        directive.$inject = ['$state', '$rootScope', '$timeout'];
        return directive;
    }
}

angular.module('ui-router-back')
    .directive('uiBack', UiBackDirective.factory())
    .run(['$rootScope',
        ($rootScope: IRootScope1) => {

            $rootScope.$on('$stateChangeSuccess',
                (event, toState, toParams, fromState, fromParams) => {
                    if ($rootScope.uiBackData == null)
                        $rootScope.uiBackData = new UiBackData();
                    $rootScope.uiBackData.fromStateName = fromState.name;
                    $rootScope.uiBackData.fromStateScroll = $(window).scrollTop();
                    $rootScope.uiBackData.fromParams = fromParams;
                });
        }]);

#1


71  

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.

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


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.

是的,在运行一个纯ui-路由器状态机时,可以让浏览器后退/前进(历史)和刷新,但这需要一点时间。

You need several components:

你需要几个组件:

  • 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时启用后退/前进按钮,因此必须根据访问的状态生成唯一的url。这些url不需要包含任何状态信息。

  • 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都与特定的状态相关,所以您需要一种方法来存储url状态对,以便您可以在通过后退/前进或刷新单击重新启动您的角化应用程序之后检索状态信息。

  • 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键控状态。如果您可以依赖HTML5,那么您可以使用HTML5 History API,但是如果您不能像我一样,那么您可以自己用几行代码实现它(参见下面)。

  • 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.

    位置服务。最后,您需要能够管理ui-router状态的更改(由代码在内部触发)和普通浏览器url更改(通常由用户单击浏览器按钮或在浏览器栏中输入内容触发)。这一切都可能有点棘手,因为很容易混淆什么触发了什么。

Here is my implementation of each of these requirements. I have bundled everything up into three services:

下面是我对每个需求的实现。我把所有的东西都打包成三种服务:

The Session Service

会话服务

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

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

这是javascript sessionStorage对象的包装器。为了清楚起见,我把它剪了下来。对此的完整解释请参见:如何使用AngularJS单页应用程序处理页面刷新

The State History Service

国家历史服务

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

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.

StateHistoryService负责存储和检索由生成的唯一url键控的历史状态。它实际上只是字典样式对象的方便包装。

The State Location Service

该州位置服务

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

The StateLocationService handles two events:

StateLocationService处理两个事件:

  • 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.

    locationChange。当浏览器位置发生改变时,通常在按下back/forward/refresh按钮时,或应用程序首次启动时,或用户在url中键入时调用。如果当前位置的状态。url存在于StateHistoryService中,然后通过ui-router的$state.go来恢复状态。

  • 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。当您在内部移动状态时,这将被调用。当前状态的名称和参数存储在由生成的url键控的StateHistoryService中。生成的url可以是您想要的任何内容,它可以以人类可读的方式标识状态,也可以不标识状态。在我的示例中,我使用状态参数加上从guid派生的随机生成的数字序列(guid生成器片段请参阅foot)。生成的url显示在浏览器栏中,最重要的是,使用@location将其添加到浏览器的内部历史堆栈中。url的url。它将url添加到浏览器的历史堆栈中,以支持向前/后退按钮。

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.

这种技术的最大问题是调用@location。stateChange方法中的url url将触发$locationChangeSuccess事件,因此调用locationChange方法。同样调用@state。从locationChange触发$stateChangeSuccess事件,即stateChange方法。这让浏览器的历史很混乱。

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.

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

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:

现在我们需要做的就是在状态转换生命周期的适当时间调用HistoryService方法。这是在AngularJS应用程序中完成的。run方法如下:

Angular app.run

角app.run

angular
.module 'app', ['ui.router']
.run ($rootScope, stateLocationService) ->

    $rootScope.$on '$stateChangeSuccess', (event, toState, toParams) ->
        stateLocationService.stateChange()

    $rootScope.$on '$locationChangeSuccess', ->
        stateLocationService.locationChange()

Generate a Guid

生成一个Guid

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.

所有这些就绪后,forward / back按钮和refresh按钮都可以正常工作。

#2


46  

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

<a href="#" ng-click="goBack()">Back</a>

#3


21  

After testing different proposals, I found that the easiest way is often the best.

在测试了不同的建议之后,我发现最简单的方法往往是最好的。

If you use angular ui-router and that you need a button to go back best is this:

如果你使用角ui-router而且你需要一个按钮来返回最好的是:

<button onclick="history.back()">Back</button>

or

<a onclick="history.back()>Back</a>

// Warning don't set the href or the path will be broken.

//警告不要设置href,否则路径将被破坏。

Explanation: Suppose a standard management application. Search object -> View object -> Edit object

说明:假设有一个标准的管理应用程序。搜索对象->视图对象->编辑对象

Using the angular solutions From this state :

利用这个状态下的角解:

Search -> View -> Edit

搜索->视图->编辑

To :

:

Search -> View

搜索- >查看

Well that's what we wanted except if now you click the browser back button you'll be there again :

这就是我们想要的但是如果你点击浏览器后退按钮你会再次出现:

Search -> View -> Edit

搜索->视图->编辑

And that is not logical

这是不合逻辑的

However using the simple solution

但是使用简单的解决方案

<a onclick="history.back()"> Back </a>

from :

来自:

Search -> View -> Edit

搜索->视图->编辑

after click on button :

点击按钮后:

Search -> View

搜索- >查看

after click on browser back button :

点击浏览器后退按钮后:

Search

搜索

Consistency is respected. :-)

一致性是尊重。:-)

#4


7  

If you are looking for the simplest "back" button, then you could set up a directive like so:

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

    .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();
          };
        }
      };
    });

Keep in mind this is a fairly unintelligent "back" button because it is using the browser's history. If you include it on your landing page, it will send a user back to any url they came from prior to landing on yours.

记住,这是一个相当不智能的“后退”按钮,因为它使用的是浏览器的历史记录。如果你将它包含在你的登陆页面上,它会将用户发送回他们在登陆之前来自的任何url。

#5


3  

browser's back/forward button solution
I encountered the same problem and I solved it using the popstate event from the $window object and ui-router's $state object. A popstate event is dispatched to the window every time the active history entry changes.
The $stateChangeSuccess and $locationChangeSuccess events are not triggered on browser's button click even though the address bar indicates the new location.
So, assuming you've navigated from states main to folder to main again, when you hit back on the browser, you should be back to the folder route. The path is updated but the view is not and still displays whatever you have on main. try this:

浏览器的后退/前进按钮解决方案遇到了相同的问题,我使用$window对象的popstate事件和ui-router的$state对象解决了这个问题。每当活动历史记录条目发生更改时,将向窗口发送popstate事件。$stateChangeSuccess和$locationChangeSuccess事件不会在浏览器的按钮单击中触发,即使地址栏显示了新的位置。所以,假设你已经从主文件夹导航到主文件夹,当你在浏览器上点击时,你应该回到文件夹路径。路径被更新了,但是视图没有并且仍然显示主界面上的所有内容。试试这个:

angular
.module 'app', ['ui.router']
.run($state, $window) {

     $window.onpopstate = function(event) {

        var stateName = $state.current.name,
            pathname = $window.location.pathname.split('/')[1],
            routeParams = {};  // i.e.- $state.params

        console.log($state.current.name, pathname); // 'main', 'folder'

        if ($state.current.name.indexOf(pathname) === -1) {
            // Optionally set option.notify to false if you don't want 
            // to retrigger another $stateChangeStart event
            $state.go(
              $state.current.name, 
              routeParams,
              {reload:true, notify: false}
            );
        }
    };
}

back/forward buttons should work smoothly after that.

后退/前进按钮在那之后应该能正常工作。

note: check browser compatibility for window.onpopstate() to be sure

注意:请检查windows .onpopstate()的浏览器兼容性以确定

#6


3  

Can be solved using a simple directive "go-back-history", this one is also closing window in case of no previous history.

可以使用一个简单的指令“返回历史”来解决,如果没有以前的历史,这个窗口也是关闭的。

Directive usage

指令的用法

<a data-go-back-history>Previous State</a>

Angular directive declaration

角指令声明

.directive('goBackHistory', ['$window', function ($window) {
    return {
        restrict: 'A',
        link: function (scope, elm, attrs) {
            elm.on('click', function ($event) {
                $event.stopPropagation();
                if ($window.history.length) {
                    $window.history.back();
                } else {
                    $window.close();  
                }
            });
        }
    };
}])

Note: Working using ui-router or not.

注意:是否使用ui-router。

#7


0  

The Back button wasn't working for me as well, but I figured out that the problem was that I had html content inside my main page, in the ui-view element.

Back按钮对我也不起作用,但我发现问题是我的主页中有html内容,在ui-view元素中。

i.e.

即。

<div ui-view>
     <h1> Hey Kids! </h1>
     <!-- More content -->
</div>

So I moved the content into a new .html file, and marked it as a template in the .js file with the routes.

因此,我将内容移动到一个新的.html文件中,并将其标记为.js文件中带有路由的模板。

i.e.

即。

   .state("parent.mystuff", {
        url: "/mystuff",
        controller: 'myStuffCtrl',
        templateUrl: "myStuff.html"
    })

#8


-1  

history.back() and switch to previous state often give effect not that you want. For example, if you have form with tabs and each tab has own state, this just switched previous tab selected, not return from form. In case nested states, you usually need so think about witch of parent states you want to rollback.

back()和切换到以前的状态通常不会产生您想要的效果。例如,如果您有带有制表符的表单,并且每个制表符都有自己的状态,那么这只是切换了之前选中的制表符,而不是从表单返回。如果是嵌套状态,通常需要考虑要回滚的父状态。

This directive solves problem

这个指令可以解决问题

angular.module('app', ['ui-router-back'])

<span ui-back='defaultState'> Go back </span>

It returns to state, that was active before button has displayed. Optional defaultState is state name that used when no previous state in memory. Also it restores scroll position

它返回到状态,该状态在按钮显示之前是活动的。可选的defaultState是在内存中没有先前状态时使用的状态名。它也恢复滚动位置

Code

代码

class UiBackData {
    fromStateName: string;
    fromParams: any;
    fromStateScroll: number;
}

interface IRootScope1 extends ng.IScope {
    uiBackData: UiBackData;
}

class UiBackDirective implements ng.IDirective {
    uiBackDataSave: UiBackData;

    constructor(private $state: angular.ui.IStateService,
        private $rootScope: IRootScope1,
        private $timeout: ng.ITimeoutService) {
    }

    link: ng.IDirectiveLinkFn = (scope, element, attrs) => {
        this.uiBackDataSave = angular.copy(this.$rootScope.uiBackData);

        function parseStateRef(ref, current) {
            var preparsed = ref.match(/^\s*({[^}]*})\s*$/), parsed;
            if (preparsed) ref = current + '(' + preparsed[1] + ')';
            parsed = ref.replace(/\n/g, " ").match(/^([^(]+?)\s*(\((.*)\))?$/);
            if (!parsed || parsed.length !== 4)
                throw new Error("Invalid state ref '" + ref + "'");
            let paramExpr = parsed[3] || null;
            let copy = angular.copy(scope.$eval(paramExpr));
            return { state: parsed[1], paramExpr: copy };
        }

        element.on('click', (e) => {
            e.preventDefault();

            if (this.uiBackDataSave.fromStateName)
                this.$state.go(this.uiBackDataSave.fromStateName, this.uiBackDataSave.fromParams)
                    .then(state => {
                        // Override ui-router autoscroll 
                        this.$timeout(() => {
                            $(window).scrollTop(this.uiBackDataSave.fromStateScroll);
                        }, 500, false);
                    });
            else {
                var r = parseStateRef((<any>attrs).uiBack, this.$state.current);
                this.$state.go(r.state, r.paramExpr);
            }
        });
    };

    public static factory(): ng.IDirectiveFactory {
        const directive = ($state, $rootScope, $timeout) =>
            new UiBackDirective($state, $rootScope, $timeout);
        directive.$inject = ['$state', '$rootScope', '$timeout'];
        return directive;
    }
}

angular.module('ui-router-back')
    .directive('uiBack', UiBackDirective.factory())
    .run(['$rootScope',
        ($rootScope: IRootScope1) => {

            $rootScope.$on('$stateChangeSuccess',
                (event, toState, toParams, fromState, fromParams) => {
                    if ($rootScope.uiBackData == null)
                        $rootScope.uiBackData = new UiBackData();
                    $rootScope.uiBackData.fromStateName = fromState.name;
                    $rootScope.uiBackData.fromStateScroll = $(window).scrollTop();
                    $rootScope.uiBackData.fromParams = fromParams;
                });
        }]);