Geri Düğmesinin bir AngularJS ui yönlendirici durum makinesiyle çalışmasını nasıl sağlayabilirim?


87

Ui -router kullanarak bir angularjs tek sayfalı uygulama uyguladım .

Başlangıçta her durumu farklı bir url kullanarak tanımladım, ancak bu dostça olmayan, GUID paketlenmiş url'ler için yapıldı.

Bu yüzden şimdi sitemi çok daha basit bir durum makinesi olarak tanımladım. Durumlar, URL'ler ile tanımlanmaz, ancak gerektiği gibi basitçe geçiş yapılır, örneğin:

İç İçe Durumları Tanımla

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'

Tanımlı Duruma Geçiş

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

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

Bu sistem çok iyi çalışıyor ve temiz sözdizimini seviyorum. Ancak, url'leri kullanmadığım için geri düğmesi çalışmıyor.

Düzenli ui yönlendirici durum makinemi nasıl koruyabilirim, ancak geri düğmesi işlevini nasıl etkinleştirebilirim?


1
"durumlar url'ler tarafından tanımlanmıyor" - ve şüphelendiğim sorununuz var. Geri düğmesi koddan oldukça korunur (aksi takdirde insanlar onu geçersiz kılar ve sorunlara neden olur). Neden SO'nun yaptığı gibi açısalın daha iyi URL'ler oluşturmasına izin vermiyorlar (Tamam, açısal kullanmıyor olabilirler, ancak URL şemaları açıklayıcıdır)?
jcollum

Ayrıca, bu soru yardımcı olabilir: stackoverflow.com/questions/13499040/…
jcollum

Ayrıca, URL'leri kullanmadığınız için bu, Z'yi belirtmek için insanların ona ulaşmak için X ve Y durumunu tıklamaları gerektiği anlamına gelmez mi? Bu can sıkıcı olabilir.
jcollum

farklı parametrelerle devletle gidecek mi? @jcollum
VijayVishnu

Hiçbir fikrim yok, bu çok uzun zaman önceydi
jcollum

Yanıtlar:


78

Not

Varyasyonlarının kullanılmasını öneren yanıtların $window.history.back()tümü, sorunun önemli bir bölümünü gözden kaçırmıştır: Geçmiş atlarken uygulamanın durumunu doğru durum konumuna nasıl geri yükleyebilirim (geri / ileri / yenileme). Bunu akılda tutarak; lütfen okumaya devam edin.


Evet, saf bir ui-routerdurum makinesini çalıştırırken tarayıcının geri / ileri (geçmiş) olması ve yenilenmesi mümkündür, ancak biraz yapmak gerekir.

Birkaç bileşene ihtiyacınız var:

  • Benzersiz URL'ler . Tarayıcı, yalnızca URL'leri değiştirdiğinizde geri / ileri düğmelerini etkinleştirir, bu nedenle ziyaret edilen durum başına benzersiz bir URL oluşturmanız gerekir. Bu url'lerin herhangi bir durum bilgisi içermesine gerek yoktur.

  • Bir Oturum Hizmeti . Üretilen her url belirli bir durumla ilişkilendirilir, bu nedenle url-durum çiftlerinizi saklamanın bir yoluna ihtiyacınız vardır, böylece açısal uygulamanız geri / ileri veya yenileme tıklamalarıyla yeniden başlatıldıktan sonra durum bilgilerini alabilirsiniz.

  • Bir Eyalet Tarihi . Benzersiz bir url ile anahtarlanmış ui-yönlendirici durumlarının basit bir sözlüğü. HTML5'e güvenebiliyorsanız HTML5 Geçmiş API'sini kullanabilirsiniz , ancak benim gibi yapamıyorsanız, birkaç satır kodda kendiniz uygulayabilirsiniz (aşağıya bakın).

  • Bir Konum Hizmeti . Son olarak, hem kodunuz tarafından dahili olarak tetiklenen ui-yönlendirici durum değişikliklerini hem de genellikle kullanıcının tarayıcı düğmelerini tıklaması veya tarayıcı çubuğuna bir şeyler yazmasıyla tetiklenen normal tarayıcı url değişikliklerini yönetebilmeniz gerekir. Bunların hepsi biraz yanıltıcı olabilir çünkü neyin neyi tetiklediği konusunda kafa karıştırmak kolaydır.

İşte bu gereksinimlerin her biri için benim uygulamam. Her şeyi üç hizmete ayırdım:

Oturum Hizmeti

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

Bu, javascript sessionStoragenesnesi için bir sarmalayıcıdır . Burada netlik için kestim. Bunun tam açıklaması için lütfen bakınız: Bir AngularJS Tek Sayfa Uygulaması ile sayfa yenilemeyi nasıl hallederim?

Devlet Tarih Servisi

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Üretilen, benzersiz URL'ler anahtarlı tarihsel devletlerin depolama ve alma sonrasında görünüyor. Gerçekten de sözlük tarzı bir nesne için kullanışlı bir paketleyicidir.

Eyalet Konum Hizmeti

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İki olayı ele alır:

  • locationChange . Bu, tarayıcının konumu değiştirildiğinde, genellikle geri / ileri / yenile düğmesine basıldığında veya uygulama ilk başladığında veya kullanıcı bir url yazdığında çağrılır. StateHistoryServiceİçinde mevcut location.url için bir durum mevcutsa, ui-router'lar aracılığıyla durumu geri yüklemek için kullanılır $state.go.

  • stateChange . Bu, durumu dahili olarak taşıdığınızda denir. Mevcut durumun adı ve parametreleri, StateHistoryServiceoluşturulan bir url tarafından anahtarlanmış olarak saklanır . Oluşturulan bu url istediğiniz herhangi bir şey olabilir, durumu insan tarafından okunabilir bir şekilde tanımlayabilir veya tanımlamayabilir. Benim durumumda bir durum parametresi artı bir kılavuzdan türetilmiş rastgele oluşturulmuş bir rakam dizisi kullanıyorum (kılavuz oluşturucu parçacığı için ayağa bakın). Oluşturulan url, tarayıcı çubuğunda görüntülenir ve en önemlisi, kullanılarak tarayıcının dahili geçmiş yığınına eklenir @location.url url. İleri / geri düğmelerini etkinleştiren tarayıcının geçmiş yığınına url'nin eklenmesi.

Bu teknikle ilgili en büyük sorun @location.url url, stateChangeyöntemi $locationChangeSuccessçağırmanın olayı tetiklemesi ve dolayısıyla locationChangeyöntemi çağırmasıdır . Aynı şekilde @state.gofrom öğesini çağırmak olayı ve dolayısıyla yöntemi locationChangetetikleyecektir . Bu çok kafa karıştırıcı hale gelir ve tarayıcı geçmişini sonu gelmez bozar.$stateChangeSuccessstateChange

Çözüm çok basit. preventCallDizinin yığın ( popve push) olarak kullanıldığını görebilirsiniz . Yöntemlerden biri her çağrıldığında, diğer yöntemin yalnızca tek seferlik olarak adlandırılmasını engeller. Bu teknik, $ olaylarının doğru tetiklenmesini engellemez ve her şeyi düz tutar.

Şimdi tek yapmamız gereken, HistoryServiceyöntemleri durum geçiş yaşam döngüsünde uygun zamanda çağırmak . Bu, AngularJS Apps .runyönteminde şu şekilde yapılır :

Açısal uygulama çalıştırma

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

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

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

Bir Kılavuz Oluşturun

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

Tüm bunlar yerine getirildiğinde, ileri / geri düğmeleri ve yenileme düğmesi beklendiği gibi çalışır.


1
Yukarıdaki SessionService örneğinde, erişimci: yönteminin `@ get / setSession 'yerine @setStorageve kullanıyor olması gerektiğini düşünüyorum @getStorage.
Peter Whitfield

3
Bu örnek için hangi dil kullanılıyor. Aşina olduğum köşeli görünmüyor.
deepak

Dil javascript, sözdizimi kahve yazıydı.
biofractal

1
@jlguenego İşlevsel bir tarayıcı geçmişi / tarayıcı ileri geri düğmeleri ve yer imi koyabileceğiniz URL'leriniz var.
Torsten Barthel

3
@jlguenego - varyasyonlarını kullanmayı öneren yanıtlar $window.history.back(), sorunun önemli bir bölümünü gözden kaçırdı. Önemli olan, uygulamanın durumunu geçmiş atladıkça (geri / ileri / yenileme) doğru durum konumuna geri yüklemekti . Bu normalde durum verilerinin URI aracılığıyla sağlanmasıyla elde edilir. Soru , (açık) URI durum verileri olmadan durum konumları arasında nasıl geçiş yapılacağını sordu . Bu kısıtlama göz önüne alındığında, sadece geri düğmesini çoğaltmak yeterli değildir, çünkü bu durum konumunu yeniden kurmak için URI durum verilerine dayanır.
biofractal

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

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

2
bunu sev! ... lol ... netlik $window.history.backiçin sihir yapmıyor $rootScope... bu yüzden isterseniz geri dönmek navbar yönerge kapsamınıza bağlanabilir.
Benjamin Conant

@BenjaminConant Bunu nasıl uygulayacağını bilmeyenler için, sadece $window.history.back();çağrılacak bir işlev koyarsınız ng-click.
chakeda

doğru rootScope yalnızca işlevi herhangi bir şablonda erişilebilir kılmak içindir
Guillaume Massé

Tavuk yemeği.
Cody

23

Farklı teklifleri test ettikten sonra, en kolay yolun çoğu zaman en iyisi olduğunu buldum.

Açısal kullanıcı arabirimi kullanıyorsanız ve geri dönmek için bir düğmeye ihtiyacınız varsa en iyisi şudur:

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

veya

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

// Uyarı href'i ayarlamayın, yoksa yol kesilir.

Açıklama: Standart bir yönetim uygulamasını varsayalım. Arama nesnesi -> Nesneyi görüntüle -> Nesneyi düzenle

Açısal çözümleri kullanma Bu durumdan:

Ara -> Görüntüle -> Düzenle

Kime:

Ara -> Görüntüle

İstediğimiz buydu, ancak şimdi tarayıcı geri düğmesini tıklarsanız, tekrar orada olacaksınız:

Ara -> Görüntüle -> Düzenle

Ve bu mantıklı değil

Ancak basit çözümü kullanarak

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

from:

Ara -> Görüntüle -> Düzenle

düğmeye tıkladıktan sonra:

Ara -> Görüntüle

tarayıcı geri düğmesine tıkladıktan sonra:

Arama

Tutarlılığa saygı duyulur. :-)


7

En basit "geri" düğmesini arıyorsanız, bunun gibi bir yönerge oluşturabilirsiniz:

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

Bunun, tarayıcının geçmişini kullandığı için oldukça akıllıca olmayan bir "geri" düğmesi olduğunu unutmayın. Bunu açılış sayfanıza eklerseniz, bir kullanıcıyı sizin sitenize gelmeden önce geldiği herhangi bir url'ye geri gönderir.


3

tarayıcının geri / ileri düğmesi çözümü
Aynı sorunla karşılaştım ve bunu popstate event$ window nesnesinden ve ui-router's $state object. Etkin geçmiş girişi her değiştiğinde pencereye bir popstate olayı gönderilir. Ve adres çubuğu yeni konumu göstermesine rağmen olaylar tarayıcının düğmesi tıklandığında tetiklenir değildir. Yani, devletler ayrılana ettik varsayarak için için size vurduğunda yine tarayıcı üzerinde, geri olmalıdır rota. Yol güncellenir, ancak görünüm değildir ve ne varsa yine de görüntülenir . bunu dene:
$stateChangeSuccess$locationChangeSuccess
mainfoldermainbackfoldermain

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

geri / ileri düğmeleri bundan sonra sorunsuz çalışmalıdır.

not: emin olmak için window.onpopstate () için tarayıcı uyumluluğunu kontrol edin


3

Basit bir yönerge "geri-geçmiş" direktifiyle çözülebilir, bu aynı zamanda geçmişin olmaması durumunda pencereyi kapatır.

Direktif kullanım

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

Açısal yönerge beyanı

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

Not: ui-router kullanarak veya kullanmadan çalışma.


0

Geri düğmesi benim için de çalışmıyordu, ancak sorunun htmlana sayfamda, ui-viewöğede içeriğin olması olduğunu anladım .

yani

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

Bu yüzden içeriği yeni bir .htmldosyaya taşıdım .jsve rotaların bulunduğu dosyada şablon olarak işaretledim .

yani

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

-1

history.back()ve önceki duruma geçmek genellikle istemediğiniz etkiyi verir. Örneğin, sekmeleri olan bir formunuz varsa ve her sekmenin kendi durumu varsa, bu sadece bir önceki sekmeyi seçer, formdan dönmez. İç içe geçmiş durumlar söz konusu olduğunda, genellikle geri almak istediğiniz ana durumların cadılarını düşünmeniz gerekir.

Bu yönerge sorunu çözer

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

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

Düğme görüntülenmeden önce aktif olan duruma geri döner . İsteğe bağlı defaultState, bellekte önceki bir durum olmadığında kullanılan durum adıdır. Ayrıca kaydırma konumunu geri yükler

Kod

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;
                });
        }]);
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.