1. ホーム
  2. javascript

[解決済み] AngularJSのui-routerステートマシンでBack Buttonを動作させるにはどうしたらいいですか?

2023-05-14 01:35:38

質問

私は、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-roterの状態マシンを維持したまま、戻るボタンの機能を有効にすることができますか?

どのように解決するのですか?

注意事項

のバリエーションを使用することを提案する回答は $window.history.back() を使うことを提案する回答はすべて、質問の重要な部分を見逃しています。どのように アプリケーションの状態を復元する を正しい状態の場所に復元する方法です。それを念頭に置いて;どうぞ、お読みください。


はい、ブラウザの戻る/進む(履歴) および更新を、純粋な ui-router ステート マシンを実行しながら、ブラウザのバック/フォワード (履歴) とリフレッシュを行うことは可能ですが、少し工夫が必要です。

いくつかのコンポーネントが必要です。

  • ユニークな URL . ブラウザはURLを変更したときだけ戻る/進むボタンを有効にするので、訪問した状態ごとに一意のURLを生成する必要があります。これらのURLは、状態情報を含む必要はありませんが。

  • セッションサービス . 生成された各URLは特定の状態に相関しているので、バック/フォワードまたはリフレッシュクリックによってangularアプリが再起動された後に状態情報を取得できるように、URL-stateのペアを保存する方法が必要です。

  • 州の歴史

    . ui-routerの状態を、ユニークなURLをキーとしたシンプルな辞書です。もし、HTML5 に依存できるのであれば HTML5 History API を使うことができますが、私のようにそれができない場合は、数行のコードで自分で実装することができます (以下を参照してください)。

  • 位置情報サービス . 最後に、あなたのコードによって内部的に引き起こされるui-routerの状態の変化と、ユーザーがブラウザのボタンをクリックしたりブラウザバーに何かを入力したりすることによって引き起こされる通常のブラウザURLの変化の両方を管理できるようにする必要があります。これは、何が何を引き起こしたかについて混乱しやすいので、すべて少し厄介になる可能性があります。

以下は、これらの要件のそれぞれに対する私の実装です。私はすべてを3つのサービスにまとめました。

セッションサービス

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 オブジェクトのラッパーです。ここでは分かりやすくするために切り捨てています。これの完全な説明については 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 は2つのイベントを処理します。

  • 位置の変更 . これはブラウザの位置が変更されたときに呼び出されます。通常は、戻る/進む/リフレッシュボタンが押されたとき、アプリが最初に起動したとき、ユーザーが URL を入力したときです。現在の location.url に対する状態が StateHistoryService にある場合は、それを使って ui-router の $state.go .

  • 状態変化 . これは内部で状態を移動させるときに呼ばれる。現在のステートの名前とパラメータは StateHistoryService に、生成された url をキーとして格納されます。この生成されたURLは何でもよく、人間が読める方法で状態を特定してもしなくてもかまいません。私の場合、状態のパラメータと、guidからランダムに生成された数字の列を使用しています(guid生成器のスニペットについては、足を参照してください)。生成されたURLはブラウザのバーに表示され、重要なことに、ブラウザの内部履歴スタックに追加されます。 @location.url url . ブラウザの履歴スタックにURLを追加することで、進む/戻るボタンが有効になります。

このテクニックの大きな問題は @location.url url の中で stateChange メソッドを実行すると $locationChangeSuccess イベントが発生し locationChange メソッドを呼び出します。同様に @state.go から locationChange がトリガーとなり $stateChangeSuccess イベントが発生するため stateChange メソッドを使用します。これは非常に混乱し、ブラウザの履歴を無制限に混乱させます。

解決策は非常に簡単です。あなたは preventCall の配列がスタックとして使用されていることがわかります ( poppush ). メソッドのうちのひとつが呼ばれるたびに、もうひとつのメソッドが一度だけ呼ばれるのを防ぎます。このテクニックは、$イベントの正しいトリガーを妨害せず、すべてをまっすぐに保ちます。

さて、私たちがすべきことは HistoryService メソッドを状態遷移のライフサイクルの適切なタイミングで呼び出すだけです。これはAngularJSのアプリで行われる .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()

Guidを生成する

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

これで、進む/戻るボタンと更新ボタンはすべて期待通りに動作するようになりました。