it-swarm.com.de

Wie bekomme ich die Zurück-Taste, um mit einer AngularJS-UI-Router-Statusmaschine zu arbeiten?

Ich habe eine Single-Page-Anwendung mit einem einzelnen Winkel implementiert, die ui-router verwendet. 

Ursprünglich identifizierte ich jeden Staat mit einer eindeutigen URL. Dies machte jedoch unfreundliche, GUID gepackte URLs.

Daher habe ich meine Site jetzt als eine viel einfachere Zustandsmaschine definiert. Die Zustände werden nicht durch URLs identifiziert, sondern bei Bedarf einfach wie folgt geändert:

Verschachtelte Staaten definieren

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'

Übergang in einen definierten Zustand

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

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

Dieses System funktioniert sehr gut und ich liebe seine saubere Syntax. Da ich jedoch keine URLs verwende, funktioniert der Zurück-Button nicht. 

Wie bewahre ich meinen ordentlichen ui-router-status-maschine auf, aktiviere aber die zurückschaltfläche? 

83
biofractal

Hinweis

Die Antworten, die auf die Verwendung von Variationen von $window.history.back() hindeuten, haben alle einen entscheidenden Teil der Frage verpasst: Wie man {den Status der Anwendung wiederherstellen an den korrekten Zustandsort zurücksetzt, wenn der Verlauf springt (vorwärts/rückwärts/aktualisieren). In diesem Sinne; bitte lesen Sie weiter.


Ja, es ist möglich, den Browser vor-/zurückzubewegen (Verlauf) und zu aktualisieren, während eine reine ui-router-Zustandsmaschine ausgeführt wird. Dies erfordert jedoch einiges an Arbeit. 

Sie benötigen mehrere Komponenten:

  • Eindeutige URLs. Der Browser aktiviert nur die Schaltflächen "Zurück" und "Vorwärts", wenn Sie die URLs ändern. Sie müssen also eine eindeutige URL pro besuchtem Status generieren. Diese URLs müssen jedoch keine Statusinformationen enthalten.

  • Ein Sitzungsdienst. Jede generierte URL ist mit einem bestimmten Status korreliert. Sie benötigen also eine Möglichkeit, Ihre URL-Status-Paare zu speichern, damit Sie die Statusinformationen abrufen können, nachdem Ihre Winkel-App durch Zurück-/Vorwärts- oder Aktualisierungsklicks neu gestartet wurde.

  • Ein Zustandsverlauf. Ein einfaches Wörterbuch der UI-Router-Zustände mit eindeutiger URL. Wenn Sie sich auf HTML5 verlassen können, können Sie die HTML5 History API verwenden. Wenn Sie dies jedoch nicht tun können, können Sie es auch selbst in ein paar Codezeilen implementieren (siehe unten).

  • Ein Ortungsdienst. Schließlich müssen Sie in der Lage sein, sowohl Statusänderungen des UI-Routers, die intern durch Ihren Code ausgelöst werden, als auch normale Browser-URL-Änderungen zu verwalten, die normalerweise vom Benutzer ausgelöst werden, indem Sie auf die Schaltflächen des Browsers klicken oder in die Browserleiste Dinge eingeben. Dies kann alles etwas knifflig werden, da es leicht ist, sich darüber zu verwechseln, was ausgelöst wurde.

Hier ist meine Umsetzung jeder dieser Anforderungen. Ich habe alles in drei Services zusammengefasst:

Der Sitzungsdienst

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

Dies ist ein Wrapper für das JavaScript-Objekt sessionStorage. Ich habe es hier aus Gründen der Klarheit reduziert. Eine vollständige Erklärung hierzu finden Sie unter: Wie gehe ich mit einer AngularJS Single Page-Anwendung um?

Der 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

StateHistoryService kümmert sich um das Speichern und Abrufen historischer Zustände, die durch generierte, eindeutige URLs eingegeben werden. Es ist wirklich nur ein praktischer Wrapper für ein Wörterbuchstilobjekt.

Der staatliche Standortdienst

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

Das StateLocationService verarbeitet zwei Ereignisse:

  • locationChange. Dies wird aufgerufen, wenn der Standort des Browsers geändert wird, in der Regel, wenn die Taste "Zurück/Vorwärts/Aktualisieren" gedrückt wird oder wenn die App zum ersten Mal gestartet wird oder wenn der Benutzer eine URL eingibt. Wenn in StateHistoryService ein Status für die aktuelle location.url vorhanden ist, wird der Status mithilfe des $state.go des ui-Routers wiederhergestellt.

  • stateChange. Dies wird aufgerufen, wenn Sie den Status intern verschieben. Der Name und die Parameter des aktuellen Zustands werden in StateHistoryService gespeichert, das von einer generierten URL eingegeben wird. Diese generierte URL kann beliebig sein, sie kann den Status auf eine für Menschen lesbare Weise identifizieren oder nicht. In meinem Fall verwende ich einen Zustandsparamit einer zufällig generierten Folge von Ziffern, die von einer Leitlinie abgeleitet wurden (siehe Fuß für den Leitfaden-Erzeuger-Schnipsel). Die generierte URL wird in der Browserleiste angezeigt und entscheidend zum internen Protokollstapel des Browsers mit @location.url url hinzugefügt. Das Hinzufügen der URL zum Verlaufsstack des Browsers, der die Vorwärts-/Zurück-Schaltflächen aktiviert.

Das große Problem bei dieser Technik besteht darin, dass beim Aufruf von @location.url url in der stateChange-Methode das $locationChangeSuccess-Ereignis ausgelöst und die locationChange-Methode aufgerufen wird. Gleiches Aufrufen des @state.go von locationChange löst das $stateChangeSuccess-Ereignis und somit die stateChange-Methode aus. Dies wird sehr verwirrend und stört den Browserverlauf ohne Ende.

Die Lösung ist sehr einfach. Sie können das preventCall-Array sehen, das als Stapel verwendet wird (pop und Push). Bei jedem Aufruf einer der Methoden wird verhindert, dass die andere Methode nur einmal aufgerufen wird. Diese Technik stört die korrekte Auslösung der $ -Ereignisse nicht und hält alles klar.

Jetzt müssen wir nur noch die HistoryService-Methoden zu einem geeigneten Zeitpunkt im Lebenszyklus des Zustandsübergangs aufrufen. Dies geschieht in der AngularJS Apps .run-Methode wie folgt:

Angular app.run

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

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

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

Guid generieren

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

Mit diesen Funktionen funktionieren die Vorwärts-/Zurück-Tasten und die Aktualisierungsschaltfläche wie erwartet.

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

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

Nachdem ich verschiedene Vorschläge getestet hatte, stellte ich fest, dass der einfachste Weg oft der beste ist.

Wenn Sie einen eckigen UI-Router verwenden, benötigen Sie eine Taste, um am besten zurückzukehren:

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

oder

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

// Die Warnung darf nicht gesetzt werden, sonst wird der Pfad unterbrochen.

Erläuterung: Angenommen, eine Standardverwaltungsanwendung . Suchobjekt -> Objekt anzeigen -> Objekt bearbeiten

Verwenden der Winkellösungen Aus diesem Zustand:

Suche -> Ansicht -> Bearbeiten

Zu:

Suche -> Ansicht

Nun, das wollten wir, außer wenn Sie jetzt auf die Schaltfläche "Zurück" des Browsers klicken, sind Sie wieder da:

Suche -> Ansicht -> Bearbeiten

Und das ist nicht logisch

jedoch mit der einfachen Lösung

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

von :

Suche -> Ansicht -> Bearbeiten

nach klick auf taste:

Suche -> Ansicht

nach dem Klick auf die Schaltfläche "Zurück" im Browser:

Suche

Konsistenz wird respektiert. :-)

22
bArraxas

Wenn Sie nach der einfachsten "Zurück" -Schaltfläche suchen, können Sie eine Anweisung wie folgt einrichten:

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

Denken Sie daran, dass dies eine ziemlich unintelligente "Zurück" -Schaltfläche ist, da diese den Verlauf des Browsers verwendet. Wenn Sie es in Ihre Landing Page aufnehmen, wird ein Benutzer zu einer beliebigen URL zurückgeleitet, von der er gekommen ist, bevor Sie auf Ihrer landen.

7
dgrant069

Browser-Zurück/Vorwärts-Tastenlösung
Ich bin auf das gleiche Problem gestoßen und habe es mit dem popstate event aus dem $ window-Objekt und ui-router's $state object gelöst. Ein Popstate-Ereignis wird bei jeder Änderung des aktiven Verlaufseintrags in das Fenster gesendet.
Die Ereignisse $stateChangeSuccess und $locationChangeSuccess werden nicht beim Klicken auf die Schaltfläche des Browsers ausgelöst, obwohl die Adressleiste den neuen Standort angibt.
Angenommen, Sie haben wieder von den Zuständen main zu folder zu main gewechselt. Wenn Sie im Browser back drücken, sollten Sie sich wieder auf der Route folder befinden. Der Pfad wird aktualisiert, aber die Ansicht ist nicht und zeigt immer noch das an, was Sie in main haben. Versuche dies:

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

die Tasten "Zurück" und "Vorwärts" funktionieren danach einwandfrei.

hinweis: Überprüfen Sie die Browserkompatibilität für window.onpopstate (), um sicherzugehen

3
jfab fab

Kann mit einer einfachen Direktive "go-back-history" gelöst werden, diese schließt auch das Fenster, wenn keine Vorgeschichte vorliegt.

Verwendung der Richtlinie

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

Winkelrichtlinie Erklärung

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

Hinweis: Arbeiten mit einem UI-Router oder nicht.

3
hthetiot

Der Zurück-Button funktionierte auch nicht für mich, aber ich fand heraus, dass das Problem darin bestand, dass ich html-Inhalt auf meiner Hauptseite hatte, im ui-view-Element.

d.h. 

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

Also habe ich den Inhalt in eine neue .html-Datei verschoben und mit den Routen als Vorlage in der .js-Datei markiert. 

d.h.

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