Bagaimana cara agar Tombol Kembali berfungsi dengan mesin status ui-router AngularJS?

87

Saya telah menerapkan aplikasi satu halaman angularjs menggunakan ui-router .

Awalnya saya mengidentifikasi setiap negara menggunakan url yang berbeda tetapi ini dibuat untuk url yang dikemas GUID yang tidak ramah.

Jadi saya sekarang telah mendefinisikan situs saya sebagai mesin negara yang jauh lebih sederhana. Status tidak diidentifikasi oleh url tetapi hanya dialihkan ke sesuai kebutuhan, seperti ini:

Tentukan Status Bersarang

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'

Transisi ke Status yang Ditentukan

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

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

Sistem ini bekerja dengan sangat baik dan saya suka sintaksnya yang bersih. Namun, karena saya tidak menggunakan url, tombol kembali tidak berfungsi.

Bagaimana cara menjaga agar mesin status ui-router saya tetap rapi tetapi mengaktifkan fungsi tombol kembali?

biofraktal
sumber
1
"negara bagian tidak diidentifikasi oleh url" - dan saya curiga ada masalah Anda. Tombol kembali cukup terlindungi dari kode (jika tidak, orang akan menimpanya, menyebabkan masalah). Mengapa tidak membiarkan angular membuat url yang lebih baik, seperti SO (OK mereka mungkin tidak menggunakan angular, tapi skema url mereka ilustratif)?
jcollum
Juga, pertanyaan ini mungkin membantu: stackoverflow.com/questions/13499040/…
jcollum
Juga, karena Anda tidak menggunakan URL, bukankah itu berarti bahwa untuk mendapatkan status Z orang harus mengklik melalui status X dan Y untuk mendapatkannya? Itu mungkin mengganggu.
jcollum
apakah itu akan berjalan dengan negara dengan parameter yang berbeda? @jcollum
VijayVishnu
Saya tidak tahu, ini sudah terlalu lama
jcollum

Jawaban:

78

Catatan

Jawaban yang menyarankan penggunaan variasi $window.history.back()semuanya telah melewatkan bagian penting dari pertanyaan: Bagaimana cara mengembalikan status aplikasi ke lokasi-negara yang benar saat sejarah melompat (mundur / maju / refresh). Dengan pemikiran itu; tolong, baca terus.


Ya, adalah mungkin untuk membuat browser mundur / maju (riwayat) dan menyegarkan saat menjalankan mesin ui-routernegara murni tetapi itu membutuhkan sedikit kerja.

Anda membutuhkan beberapa komponen:

  • URL unik . Browser hanya mengaktifkan tombol mundur / maju saat Anda mengubah url, jadi Anda harus membuat url unik untuk setiap status yang dikunjungi. Url ini tidak perlu berisi informasi negara bagian apa pun.

  • Sebuah Layanan Sesi . Setiap url yang dihasilkan berkorelasi dengan kondisi tertentu sehingga Anda memerlukan cara untuk menyimpan pasangan status-url Anda sehingga Anda dapat mengambil informasi status setelah aplikasi sudut Anda dimulai ulang dengan klik mundur / maju atau segarkan.

  • Sejarah Negara . Kamus sederhana dari status ui-router yang dikunci oleh url unik. Jika Anda dapat mengandalkan HTML5 maka Anda dapat menggunakan API Riwayat HTML5 , tetapi jika, seperti saya, Anda tidak dapat menerapkannya sendiri dalam beberapa baris kode (lihat di bawah).

  • Sebuah Layanan Lokasi . Terakhir, Anda harus dapat mengelola kedua perubahan status ui-router, yang dipicu secara internal oleh kode Anda, dan perubahan url browser normal biasanya dipicu oleh pengguna yang mengklik tombol browser atau mengetik sesuatu ke dalam bilah browser. Ini semua bisa menjadi sedikit rumit karena mudah untuk menjadi bingung tentang apa yang memicu apa.

Berikut adalah implementasi saya untuk masing-masing persyaratan tersebut. Saya telah menggabungkan semuanya menjadi tiga layanan:

Layanan Sesi

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

Ini adalah pembungkus untuk sessionStorageobjek javascript . Saya telah memotongnya untuk kejelasan di sini. Untuk penjelasan lengkap tentang ini, silakan lihat: Bagaimana cara menangani penyegaran halaman dengan Aplikasi Halaman Tunggal AngularJS

Layanan Sejarah Negara

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

The StateHistoryServiceterlihat setelah penyimpanan dan pengambilan negara sejarah mengetik dengan yang dihasilkan, url yang unik. Ini benar-benar hanya pembungkus kenyamanan untuk objek gaya kamus.

Layanan Lokasi Negara

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

The StateLocationServicemenangani dua peristiwa:

  • locationChange . Ini dipanggil saat lokasi browser diubah, biasanya saat tombol kembali / maju / segarkan ditekan atau saat aplikasi pertama kali dimulai atau saat pengguna mengetikkan url. Jika status untuk location.url saat ini ada, StateHistoryServicemaka status itu digunakan untuk memulihkan status melalui ui-router $state.go.

  • stateChange . Ini dipanggil saat Anda memindahkan status secara internal. Nama negara bagian saat ini dan params disimpan dalam StateHistoryServicekunci oleh url yang dihasilkan. Url yang dihasilkan ini dapat berupa apa pun yang Anda inginkan, mungkin atau mungkin tidak mengidentifikasi negara dengan cara yang dapat dibaca manusia. Dalam kasus saya, saya menggunakan param negara ditambah urutan digit yang dihasilkan secara acak yang berasal dari pedoman (lihat kaki untuk cuplikan generator pedoman). Url yang dihasilkan ditampilkan di bilah browser dan, yang terpenting, ditambahkan ke tumpukan riwayat internal browser menggunakan @location.url url. Ini menambahkan url ke tumpukan riwayat browser yang memungkinkan tombol maju / mundur.

Masalah besar dengan teknik ini adalah bahwa menelepon @location.url urldalam stateChangemetode akan memicu $locationChangeSuccessacara dan memanggil locationChangemetode. Dengan cara yang sama memanggil @state.gofrom locationChangeakan memicu $stateChangeSuccesskejadian dan begitu pula stateChangemetode. Ini menjadi sangat membingungkan dan mengacaukan riwayat browser tanpa akhir.

Solusinya sangat sederhana. Anda dapat melihat preventCallarray yang digunakan sebagai tumpukan ( popdan push). Setiap kali salah satu metode dipanggil, ia mencegah metode lain disebut hanya satu kali. Teknik ini tidak mengganggu pemicu peristiwa $ yang benar dan menjaga semuanya tetap lurus.

Sekarang yang perlu kita lakukan adalah memanggil HistoryServicemetode pada waktu yang tepat dalam siklus hidup transisi status. Ini dilakukan dalam .runmetode Aplikasi AngularJS , seperti ini:

App.run sudut

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

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

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

Hasilkan Panduan

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

Dengan semua ini di tempatnya, tombol maju / mundur dan tombol segarkan semuanya berfungsi seperti yang diharapkan.

biofraktal
sumber
1
dalam contoh SessionService di atas, saya pikir metode accessor: harus menggunakan @setStoragedan @getStoragedaripada `@ get / setSession '.
Peter Whitfield
3
Bahasa apa yang digunakan untuk contoh ini. Sepertinya tidak bersudut yang saya kenal.
deepak
Bahasanya javascript, sintaksnya adalah coffeescript.
biofraktal
1
@jlguenego Anda memiliki riwayat browser fungsional / bowser bolak-balik tombol dan URL yang dapat Anda tandai.
Torsten Barthel
3
@jlguenego - jawaban yang menyarankan penggunaan variasi $window.history.back()telah melewatkan bagian penting dari pertanyaan. Intinya adalah untuk mengembalikan aplikasi negara ke negara-lokasi yang benar sebagai sejarah melompat (belakang / maju / refresh). Ini biasanya dicapai dengan menyediakan data status melalui URI. Pertanyaan tersebut menanyakan bagaimana cara melompat di antara lokasi negara bagian tanpa data status URI (eksplisit). Dengan adanya batasan ini, tidak cukup hanya mereplikasi tombol kembali karena ini bergantung pada data status URI untuk membangun kembali status-lokasi.
biofraktal
46
app.run(['$window', '$rootScope', 
function ($window ,  $rootScope) {
  $rootScope.goBack = function(){
    $window.history.back();
  }
}]);

<a href="#" ng-click="goBack()">Back</a>
Guillaume Massé
sumber
2
suka ini! ... lol ... untuk kejelasan $window.history.backadalah melakukan keajaiban bukan $rootScope... jadi kembali bisa terikat ke lingkup direktif navbar Anda jika Anda mau.
Benjamin Conant
@BenjaminConant Untuk orang yang tidak tahu bagaimana mengimplementasikan ini, Anda cukup meletakkan $window.history.back();fungsi in untuk dipanggil ng-click.
chakeda
rootScope yang benar hanya untuk membuat fungsi dapat diakses di template mana pun
Guillaume Massé
Chicken Dinner.
Cody
23

Setelah menguji berbagai proposal, saya menemukan bahwa cara termudah seringkali yang terbaik.

Jika Anda menggunakan angular ui-router dan Anda memerlukan tombol untuk kembali terbaik adalah ini:

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

atau

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

// Peringatan jangan setel href atau jalurnya akan rusak.

Penjelasan: Misalkan aplikasi manajemen standar. Cari objek -> Lihat objek -> Edit objek

Menggunakan solusi sudut Dari keadaan ini:

Cari -> Tampilan -> Edit

Kepada:

Cari -> Lihat

Nah itulah yang kami inginkan kecuali jika sekarang Anda mengklik tombol kembali browser Anda akan berada di sana lagi:

Cari -> Tampilan -> Edit

Dan itu tidak logis

Namun menggunakan solusi sederhana

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

dari:

Cari -> Tampilan -> Edit

setelah klik tombol:

Cari -> Lihat

setelah klik tombol kembali browser:

Cari

Konsistensi dihormati. :-)

bArraxas
sumber
7

Jika Anda mencari tombol "kembali" yang paling sederhana, Anda dapat menyiapkan perintah seperti ini:

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

Ingatlah bahwa ini adalah tombol "kembali" yang cukup tidak cerdas karena menggunakan riwayat browser. Jika Anda menyertakannya di halaman arahan Anda, itu akan mengirim pengguna kembali ke url mana pun mereka berasal sebelum mendarat di url Anda.

dgrant069.dll
sumber
3

solusi tombol mundur / maju browser
Saya mengalami masalah yang sama dan saya menyelesaikannya menggunakan popstate eventdari objek $ window dan ui-router's $state object. Acara popstate dikirim ke jendela setiap kali entri riwayat aktif berubah.
Acara $stateChangeSuccessdan $locationChangeSuccesstidak dipicu pada klik tombol browser meskipun bilah alamat menunjukkan lokasi baru.
Jadi, dengan asumsi Anda sudah navigasikan dari negara-negara mainuntuk folderke mainlagi, ketika anda menekan backpada browser, Anda harus kembali ke folderrute. Path diperbarui tetapi tampilan tidak dan masih menampilkan apa pun yang Anda miliki main. coba ini:

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

tombol mundur / maju harus bekerja dengan lancar setelah itu.

catatan: periksa kompatibilitas browser untuk window.onpopstate () untuk memastikan

jfab hebat
sumber
3

Dapat diselesaikan dengan petunjuk sederhana "kembali-sejarah", yang satu ini juga menutup jendela jika tidak ada riwayat sebelumnya.

Penggunaan arahan

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

Deklarasi direktif sudut

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

Catatan: Bekerja menggunakan ui-router atau tidak.

hthetiot.dll
sumber
0

Tombol Kembali juga tidak berfungsi untuk saya, tetapi saya menemukan bahwa masalahnya adalah saya memiliki htmlkonten di dalam halaman utama saya, di ui-viewelemen.

yaitu

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

Jadi saya memindahkan konten ke .htmlfile baru , dan menandainya sebagai template di .jsfile dengan rute.

yaitu

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

history.back()dan beralih ke kondisi sebelumnya seringkali memberikan efek yang tidak diinginkan. Misalnya, jika Anda memiliki formulir dengan tab dan setiap tab memiliki statusnya sendiri, ini hanya mengganti tab sebelumnya yang dipilih, bukan kembali dari formulir. Dalam kasus status bersarang, Anda biasanya perlu memikirkan tentang witch status induk yang ingin Anda kembalikan.

Arahan ini memecahkan masalah

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

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

Ini kembali ke keadaan, yang aktif sebelum tombol ditampilkan. Opsional defaultStateadalah nama negara yang digunakan ketika tidak ada keadaan sebelumnya di memori. Juga mengembalikan posisi gulir

Kode

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;
                });
        }]);
Sel
sumber