Zaimplementowałem jednostronicową aplikację angularjs przy użyciu routera ui .
Początkowo zidentyfikowałem każdy stan za pomocą odrębnego adresu URL, ale spowodowało to nieprzyjazne adresy URL z GUID.
Więc teraz zdefiniowałem moją witrynę jako znacznie prostszą maszynę stanu. Stany nie są identyfikowane przez adresy URL, ale są po prostu przenoszone zgodnie z wymaganiami, na przykład:
Zdefiniuj stany zagnieżdżone
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'
Przejście do określonego stanu
#The ui-sref attrib transitions to the 'folder' state
a(ui-sref="folder({locationId:'{{folder.Id}}'})")
| {{ folder.Name }}
Ten system działa bardzo dobrze i uwielbiam jego czystą składnię. Ponieważ jednak nie używam adresów URL, przycisk Wstecz nie działa.
Jak zachować schludny komputer stanu routera ui, ale włączyć funkcję przycisku Wstecz?
javascript
angularjs
coffeescript
angular-ui-router
biofraktal
źródło
źródło
Odpowiedzi:
Uwaga
Odpowiedzi, które sugerują użycie wariacji
$window.history.back()
, pominęły kluczową część pytania: Jak przywrócić stan aplikacji do prawidłowej lokalizacji stanu, gdy historia przeskakuje (wstecz / dalej / odświeżanie). Pamiętając o tym; proszę, czytaj dalej.Tak, możliwe jest, aby przeglądarka przeszła wstecz / do przodu (historia) i odświeżyła się podczas uruchamiania czystego
ui-router
automatu stanowego, ale zajmuje to trochę czasu.Potrzebujesz kilku komponentów:
Unikalne adresy URL . Przeglądarka włącza przyciski Wstecz / Dalej tylko podczas zmiany adresów URL, więc musisz wygenerować unikalny adres URL dla każdego odwiedzonego stanu. Te adresy URL nie muszą jednak zawierać żadnych informacji o stanie.
Usługa sesji . Każdy wygenerowany adres URL jest skorelowany z określonym stanem, więc potrzebujesz sposobu na przechowywanie par stanu adresu URL, aby można było pobrać informacje o stanie po ponownym uruchomieniu aplikacji kątowej za pomocą kliknięć wstecz / do przodu lub odświeżania.
Historia stanu . Prosty słownik stanów routera ui z kluczem unikalnym adresem URL. Jeśli możesz polegać na HTML5, możesz użyć interfejsu API historii HTML5 , ale jeśli tak jak ja nie możesz, możesz zaimplementować go samodzielnie w kilku wierszach kodu (patrz poniżej).
Usługa lokalizacji . Wreszcie, musisz mieć możliwość zarządzania zarówno zmianami stanu routera ui, wywoływanymi wewnętrznie przez Twój kod, jak i normalnymi zmianami adresu URL przeglądarki, zwykle wywoływanymi przez użytkownika klikającego przyciski przeglądarki lub wpisującego coś na pasku przeglądarki. To wszystko może być nieco skomplikowane, ponieważ łatwo jest zdezorientować, co spowodowało co.
Oto moja implementacja każdego z tych wymagań. Powiązałem wszystko w trzy usługi:
Usługa sesji
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
To jest opakowanie dla
sessionStorage
obiektu javascript . Obciąłem to dla jasności. Aby uzyskać pełne wyjaśnienie, zobacz: Jak obsłużyć odświeżanie strony za pomocą aplikacji jednostronicowej AngularJSUsługa historii stanu
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
Do
StateHistoryService
dba przechowywania i udostępniania stanów historycznych kluczach będących generowanych, unikalnych adresów URL. Tak naprawdę jest to po prostu wygodna aplikacja dla obiektu w stylu słownika.Stanowa usługa lokalizacji
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
Obsługuje dwa zdarzenia:locationChange . Jest to wywoływane w przypadku zmiany lokalizacji przeglądarki, zwykle po naciśnięciu przycisku Wstecz / Dalej / Odśwież lub po pierwszym uruchomieniu aplikacji lub gdy użytkownik wpisze adres URL. Jeśli stan dla bieżącej lokalizacji.url istnieje w,
StateHistoryService
to jest używany do przywrócenia stanu za pośrednictwem routera ui$state.go
.stateChange . Nazywa się to, gdy przenosisz stan wewnętrznie. Nazwa i parametry bieżącego stanu są przechowywane w
StateHistoryService
kluczu przez wygenerowany adres URL. Ten wygenerowany adres URL może być dowolny, może, ale nie musi, identyfikować stan w sposób czytelny dla człowieka. W moim przypadku używam parametru stanu oraz losowo generowanej sekwencji cyfr pochodzących z guid (patrz stopa dla fragmentu generatora guid). Wygenerowany adres URL jest wyświetlany na pasku przeglądarki i, co najważniejsze, dodawany do wewnętrznego stosu historii przeglądarki za pomocą@location.url url
. Dodaje adres URL do stosu historii przeglądarki, który włącza przyciski Dalej / Wstecz.Duży problem z tej techniki jest to, że wywołanie
@location.url url
wstateChange
metodzie wywoła$locationChangeSuccess
zdarzenie i tak wywołaćlocationChange
metodę. Równie wywołanie@state.go
fromlocationChange
wyzwoli$stateChangeSuccess
zdarzenie, a więcstateChange
metodę. To staje się bardzo mylące i bez końca psuje historię przeglądarki.Rozwiązanie jest bardzo proste. Możesz zobaczyć
preventCall
tablicę używaną jako stos (pop
ipush
). Za każdym razem, gdy wywoływana jest jedna z metod, zapobiega to jednorazowemu wywołaniu drugiej metody. Ta technika nie koliduje z prawidłowym wyzwalaniem zdarzeń $ i utrzymuje wszystko prosto.Teraz wszystko, co musimy zrobić, to wywołać
HistoryService
metody w odpowiednim momencie cyklu życia zmiany stanu. Odbywa się to w.run
metodzie AngularJS Apps , na przykład:Angular app.run
angular .module 'app', ['ui.router'] .run ($rootScope, stateLocationService) -> $rootScope.$on '$stateChangeSuccess', (event, toState, toParams) -> stateLocationService.stateChange() $rootScope.$on '$locationChangeSuccess', -> stateLocationService.locationChange()
Wygeneruj Guid
Math.guid = -> s4 = -> Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1) "#{s4()}#{s4()}-#{s4()}-#{s4()}-#{s4()}-#{s4()}#{s4()}#{s4()}"
Po tym wszystkim przyciski do przodu / do tyłu i przycisk odświeżania działają zgodnie z oczekiwaniami.
źródło
@setStorage
i@getStorage
zamiast `@ get / setSession '.$window.history.back()
kluczową część pytania. Chodziło o to, aby przywrócić aplikacji stanu do właściwego państwowego miejscu jak historia skoków (tył / przód / odświeżania). Zwykle można to osiągnąć, dostarczając dane stanu za pośrednictwem identyfikatora URI. Pytanie dotyczyło sposobu przeskakiwania między lokalizacjami stanu bez (jawnych) danych stanu URI. Biorąc pod uwagę to ograniczenie, nie wystarczy po prostu zreplikować przycisku Wstecz, ponieważ opiera się on na danych stanu URI w celu ponownego ustanowienia lokalizacji stanu.app.run(['$window', '$rootScope', function ($window , $rootScope) { $rootScope.goBack = function(){ $window.history.back(); } }]); <a href="#" ng-click="goBack()">Back</a>
źródło
$window.history.back
robi magię, a nie$rootScope
... więc powrót może być związany z zakresem dyrektywy navbar, jeśli chcesz.$window.history.back();
funkcję, która ma zostać wywołanang-click
.Po przetestowaniu różnych propozycji stwierdziłem, że najłatwiejszy sposób jest często najlepszy.
Jeśli używasz kątowego routera ui i potrzebujesz przycisku, aby wrócić najlepiej, to:
<button onclick="history.back()">Back</button>
lub
// Uwaga, nie ustawiaj atrybutu href, bo ścieżka zostanie uszkodzona.
Objaśnienie: Załóżmy, że jest to standardowa aplikacja do zarządzania. Wyszukaj obiekt -> Wyświetl obiekt -> Edytuj obiekt
Korzystanie z rozwiązań kątowych Z tego stanu:
Do :
Chcieliśmy tego, z wyjątkiem tego, że teraz klikniesz przycisk Wstecz w przeglądarce, a będziesz tam ponownie:
I to nie jest logiczne
Jednak używając prostego rozwiązania
<a onclick="history.back()"> Back </a>
z :
po kliknięciu przycisku:
po kliknięciu przycisku Wstecz w przeglądarce:
Szanowana jest spójność. :-)
źródło
Jeśli szukasz najprostszego przycisku „Wstecz”, możesz ustawić taką dyrektywę:
.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(); }; } }; });
Należy pamiętać, że jest to dość nieinteligentny przycisk „Wstecz”, ponieważ korzysta z historii przeglądarki. Jeśli umieścisz go na swojej stronie docelowej, odeśle użytkownika z powrotem do dowolnego adresu URL, z którego przyszedł przed wylądowaniem na Twoim.
źródło
rozwiązanie przycisku wstecz / dalej przeglądarki
Napotkałem ten sam problem i rozwiązałem go za pomocą obiektu
popstate event
z $ window iui-router's $state object
. Zdarzenie popstate jest wywoływane do okna za każdym razem, gdy zmienia się aktywny wpis historii.Te
$stateChangeSuccess
i$locationChangeSuccess
zdarzenia nie są wywoływane na przeglądarki kliknięcia przycisku chociaż pasek adresu wskazuje nową lokalizację.Tak więc, zakładając, że już żeglował od stanów
main
dofolder
celumain
ponownie, gdy trafiszback
na przeglądarce, powinieneś być z powrotem nafolder
trasie. Ścieżka jest aktualizowana, ale widok nie jest i nadal wyświetla wszystko, co maszmain
. Spróbuj tego: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} ); } }; }
przyciski wstecz / dalej powinny działać płynnie po tym.
uwaga: aby mieć pewność, sprawdź zgodność przeglądarki dla window.onpopstate ()
źródło
Można to rozwiązać za pomocą prostej dyrektywy „powrót do historii”, która również zamyka okno w przypadku braku historii.
Stosowanie dyrektywy
<a data-go-back-history>Previous State</a>
Deklaracja dyrektywy 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(); } }); } }; }])
Uwaga: praca z routerem ui lub nie.
źródło
Przycisk Wstecz również nie działał dla mnie, ale zorientowałem się, że problem polegał na tym, że miałem
html
zawartość wewnątrz mojej strony głównej, wui-view
elemencie.to znaczy
<div ui-view> <h1> Hey Kids! </h1> <!-- More content --> </div>
Dlatego przeniosłem zawartość do nowego
.html
pliku i oznaczyłem ją jako szablon w.js
pliku z trasami.to znaczy
.state("parent.mystuff", { url: "/mystuff", controller: 'myStuffCtrl', templateUrl: "myStuff.html" })
źródło
history.back()
i przejdź do poprzedniego stanu często daje efekt nie taki, jakiego chcesz. Na przykład, jeśli masz formularz z zakładkami, a każda karta ma swój własny stan, to właśnie zmieniło wybraną poprzednią kartę, a nie powróci z formularza. W przypadku stanów zagnieżdżonych zwykle potrzebujesz, więc pomyśl o wiedźmie ze stanów macierzystych, które chcesz wycofać.Ta dyrektywa rozwiązuje problem
angular.module('app', ['ui-router-back']) <span ui-back='defaultState'> Go back </span>
Powraca do stanu, który był aktywny przed wyświetleniem przycisku. Opcjonalny
defaultState
jest nazwa stanu używana, gdy w pamięci nie ma poprzedniego stanu. Przywraca również pozycję przewijaniaKod
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; }); }]);
źródło