Как получить кнопку "Назад" для работы с автозагрузчиком 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;
});
}]);