Как получить кнопку "Назад" для работы с автозагрузчиком UL-маршрутизатора AngularJS?

Я применил одностраничное приложение angularjs, используя ui-router.

Изначально я определял каждое состояние с использованием отдельного 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 }}

Эта система работает очень хорошо, и мне нравится ее чистый синтаксис. Однако, поскольку я не использую URL-адреса, кнопка возврата не работает.

Как сохранить мой аккуратный пользовательский компьютер ui-router, но активировать функцию кнопки "Назад"?

Ответ 1

Примечание

Ответы, предлагающие использование вариантов $window.history.back(), все пропустили ключевую часть вопроса: Как восстановить состояние приложения в правильном состоянии - как скачки истории (back/forward/refresh). С этим в мыслях; пожалуйста, читайте дальше.


Да, возможно иметь браузер назад/вперед (история) и обновляться при запуске чистых состояний ui-router, но это требует немного усилий.

Вам нужны несколько компонентов:

  • Уникальные URL-адреса. Браузер позволяет использовать кнопки "назад/вперед" при изменении URL-адресов, поэтому вы должны создать уникальный URL-адрес для каждого посещенного состояния. Эти URL-адреса не обязательно содержат информацию о состоянии.

  • Служба сеанса. Каждый сгенерированный URL-адрес сопоставляется с конкретным состоянием, поэтому вам нужен способ хранения ваших пар URL-адресов, чтобы вы могли получать информацию о состоянии после того, как ваше приложение angular было перезапущено с помощью обратного/переадресационного или обновления кликов.

  • История государства. Простой словарь состояний ui-router с помощью уникального URL-адреса. Если вы можете положиться на HTML5, вы можете использовать API истории HTML5, но если, как и я, вы не сможете его реализовать себя в нескольких строках кода (см. ниже).

  • Служба определения местоположения. Наконец, вы должны иметь возможность управлять изменениями состояния 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

Это оболочка для объекта javascript sessionStorage. Я здесь сократил это для ясности. Для полного объяснения этого, пожалуйста, см. Как обработать обновление страницы с помощью приложения Singleular Application 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 отслеживает хранение и извлечение исторических состояний с помощью генерируемых уникальных URL-адресов. Это действительно просто удобная оболочка для объекта словарного стиля.

Служба определения местоположения

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-адреса. Если в текущем location.url существует состояние StateHistoryService, то оно используется для восстановления состояния через ui-router $state.go.

  • StateChange. Это вызывается, когда вы перемещаете состояние внутри страны. Текущее имя и параметры состояния хранятся в StateHistoryService, заданных сгенерированным URL-адресом. Этот сгенерированный URL-адрес может быть любым, что вы хотите, он может или не может идентифицировать состояние по-человечески читаемому. В моем случае я использую параметр состояния плюс произвольно сгенерированную последовательность цифр, полученных из guid (см. Фут для фрагмента генератора ключей). Сгенерированный URL-адрес отображается в строке браузера и, что важно, добавляется в стек внутренней истории браузера с помощью @location.url url. Он добавляет URL-адрес в стек истории браузера, который позволяет использовать кнопки "вперед/назад".

Большая проблема с этим методом заключается в том, что вызов @location.url url в методе stateChange вызывает событие $locationChangeSuccess и поэтому вызывает метод locationChange. Аналогично вызов @state.go из locationChange вызовет событие $stateChangeSuccess и, следовательно, метод stateChange. Это становится очень запутанным и бесполезно завершает историю браузера.

Решение очень простое. Вы можете увидеть массив preventCall, используемый как стек (pop и push). Каждый раз, когда один из методов называется, он запрещает другой метод, который вызывается одноразовым. Этот метод не мешает правильному запуску событий $и сохраняет все прямо.

Теперь все, что нам нужно сделать, это вызвать методы 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()

Создать руководство

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

При этом все кнопки вперед/назад и кнопка обновления работают как ожидалось.

Ответ 2

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

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

Ответ 3

После тестирования различных предложений я обнаружил, что самый простой способ - это лучший результат.

Если вы используете angular ui-router и вам нужна кнопка, чтобы вернуться назад, это:

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

или

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

//Предупреждение не устанавливает href, или путь будет нарушен.

Объяснение: Предположим, что стандартное приложение управления. Объект поиска → Просмотреть объект → Изменить объект

Использование решений angular Из этого состояния:

Поиск → Просмотр → Изменить

To:

Поиск → Просмотр

Хорошо, что мы хотели, если вы сейчас нажмете кнопку "Назад в браузере", вы снова будете там:

Поиск → Просмотр → Изменить

И это не логично

Однако, используя простое решение

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

from:

Поиск → Просмотр → Изменить

после нажатия кнопки:

Поиск → Просмотр

после нажатия кнопки "Назад назад":

Поиск

Согласованность соблюдается.: -)

Ответ 4

Если вы ищете простую кнопку "назад", вы можете настроить такую ​​директиву:

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

Помните, что это довольно неинтеллектуальная кнопка "назад", потому что она использует историю браузера. Если вы включите его на целевой странице, он отправит пользователя обратно на любой URL-адрес, с которого они пришли, до посадки на вашем.

Ответ 5

решение для браузера назад/вперед
Я столкнулся с той же проблемой, и решил ее с помощью popstate event из объекта $window и ui-router $state object. Событие popstate отправляется в окно каждый раз, когда изменяется запись активной истории.
События $stateChangeSuccess и $locationChangeSuccess не запускаются при нажатии кнопки браузера, даже если адресная строка указывает новое местоположение.
Итак, если вы снова перешли от состояний от main до folder до main, когда вы нажмете back в браузере, вы вернетесь к маршруту folder. Путь обновляется, но вид не отображается и все еще отображает все, что у вас есть на main. попробуйте следующее:

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

назад/вперед кнопки должны работать плавно после этого.

Примечание: проверьте совместимость браузера для window.onpopstate().

Ответ 6

Может быть решена с помощью простой директивы "back-back-history", которая также закрывает окно в случае отсутствия предыдущей истории.

Использование директивы

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

Angular объявление директивы

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

Примечание. Работа с использованием ui-router или нет.

Ответ 7

Кнопка "Назад" тоже не работала для меня, но я понял, что проблема в том, что у меня было html контент внутри моей главной страницы, в элементе ui-view.

то есть.

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

Итак, я переместил содержимое в новый .html файл и пометил его как шаблон в файле .js с помощью маршрутов.

то есть.

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

Ответ 8

history.back() и переключиться на предыдущее состояние часто дают эффект, которого вы не хотите. Например, если у вас есть форма с вкладками, и каждая вкладка имеет собственное состояние, это только что выбрала предыдущую вкладку, а не возврат из формы. В случае вложенных состояний вам обычно нужно подумать о ведьме родительских состояний, которые вы хотите откат.

Эта директива решает проблему

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

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

Возврат к состоянию, которое было активным до. Необязательный defaultState - это имя состояния, которое используется при отсутствии предыдущего состояния в памяти. Также он восстанавливает положение прокрутки

Код

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;
                });
        }]);