Я реализовал одностраничное приложение angularjs с использованием ui-router .
Первоначально я идентифицировал каждое состояние, используя отдельный URL-адрес, однако это было сделано для недружественных 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'
Переход к определенному состоянию
#The ui-sref attrib transitions to the 'folder' state
a(ui-sref="folder({locationId:'{{folder.Id}}'})")
| {{ folder.Name }}
Эта система работает очень хорошо, и мне нравится ее чистый синтаксис. Однако, поскольку я не использую URL-адреса, кнопка «Назад» не работает.
Как мне сохранить мой аккуратный конечный автомат ui-router, но включить функцию кнопки возврата?
javascript
angularjs
coffeescript
angular-ui-router
биофрактал
источник
источник
Ответы:
Заметка
Все ответы, предлагающие использовать варианты
$window.history.back()
, упускают из виду важную часть вопроса: как восстановить состояние приложения до правильного местоположения состояния при переходе истории (назад / вперед / обновить). С этим в мыслях; пожалуйста, читайте дальше.Да, можно переключать браузер назад / вперед (история) и обновлять во время работы чистого
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
Это оболочка для
sessionStorage
объекта javascript . Я сократил это здесь для ясности. Полное объяснение этого вопроса см. В разделе: Как обрабатывать обновление страницы с помощью одностраничного приложения 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
внешнем виде после хранения и извлечения исторических состояний введенных пользователя с помощью сгенерированных уникальных адресов. На самом деле это просто удобная оболочка для объекта словарного стиля.Государственная служба геолокации
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 (фрагмент генератора guid см. В Foot). Сгенерированный URL-адрес отображается в строке браузера и, что особенно важно, добавляется во внутренний стек истории браузера с помощью@location.url url
. Он добавляет URL-адрес в стек истории браузера, который включает кнопки вперед / назад.Большая проблема с этой техникой, что вызов
@location.url url
вstateChange
методе вызовет$locationChangeSuccess
событие и так вызовитеlocationChange
метод. В равной степени вызов@state.go
fromlocationChange
вызовет$stateChangeSuccess
событие, а значит, иstateChange
метод. Это очень сбивает с толку и бесконечно портит историю браузера.Решение очень простое. Вы можете видеть, что
preventCall
массив используется как стек (pop
иpush
). Каждый раз, когда вызывается один из методов, он предотвращает одноразовый вызов другого метода. Этот метод не мешает правильному запуску событий $ и сохраняет все в порядке.Теперь все, что нам нужно сделать, это вызвать
HistoryService
методы в подходящее время в жизненном цикле перехода между состояниями. Это делается в.run
методе AngularJS Apps , например: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()}"
После всего этого кнопки вперед / назад и кнопка обновления работают должным образом.
источник
@setStorage
and,@getStorage
а не `@ get / setSession '.$window.history.back()
, упускают важную часть вопроса. Дело в том, чтобы восстановить приложения состояния для правильного государственного места , как история скачки (назад / вперед / обновления). Обычно это достигается путем предоставления данных о состоянии через URI. Был задан вопрос, как переключаться между местоположениями состояний без (явных) данных состояния URI. Учитывая это ограничение, недостаточно просто реплицировать кнопку «Назад», потому что при этом используются данные состояния URI для восстановления местоположения состояния.app.run(['$window', '$rootScope', function ($window , $rootScope) { $rootScope.goBack = function(){ $window.history.back(); } }]); <a href="#" ng-click="goBack()">Back</a>
источник
$window.history.back
волшебство не$rootScope
творится ... так что возврат можно привязать к области действия вашей навигационной панели, если хотите.$window.history.back();
функцию, которая будет вызванаng-click
.После тестирования различных предложений я обнаружил, что самый простой способ зачастую оказывается лучшим.
Если вы используете угловой ui-router и вам нужна кнопка для возврата, лучше всего это:
<button onclick="history.back()">Back</button>
или
// Предупреждение, не устанавливайте href, иначе путь будет нарушен.
Объяснение: Предположим, стандартное приложение для управления. Поиск объекта -> Просмотр объекта -> Редактировать объект
Использование угловых решений Из этого состояния:
Кому:
Что ж, это то, что мы хотели, за исключением того, что если теперь вы нажмете кнопку возврата в браузере, вы снова будете там:
И это не логично
Однако с помощью простого решения
<a onclick="history.back()"> Back </a>
из :
после нажатия на кнопку:
после нажатия кнопки возврата в браузере:
Последовательность соблюдается. :-)
источник
Если вы ищете самую простую кнопку «назад», вы можете настроить такую директиву:
.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-адрес, с которого он пришел до перехода на ваш.
источник
Решение кнопки браузера «назад / вперед».
Я столкнулся с той же проблемой и решил ее, используя
popstate event
объект from $ window иui-router's $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 (), чтобы быть уверенным
источник
Может быть решена с помощью простой директивы «go-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 или нет.
источник
Кнопка "Назад" у меня тоже не работала, но я понял, что проблема в том, что у меня есть
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" })
источник
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; }); }]);
источник