[解決済み] AngularJSのui-routerステートマシンでBack Buttonを動作させるにはどうしたらいいですか?
質問
私は、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
の配列がスタックとして使用されていることがわかります (
pop
と
push
). メソッドのうちのひとつが呼ばれるたびに、もうひとつのメソッドが一度だけ呼ばれるのを防ぎます。このテクニックは、$イベントの正しいトリガーを妨害せず、すべてをまっすぐに保ちます。
さて、私たちがすべきことは
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()}"
これで、進む/戻るボタンと更新ボタンはすべて期待通りに動作するようになりました。
関連
-
[解決済み] JavaScriptで現在のURLを取得する?
-
[解決済み] JavaScriptで現在の日付を取得するには?
-
[解決済み] セレクタの子を取得する方法は?
-
[解決済み] AngularJSでデータバインディングはどのように機能するのですか?
-
[解決済み] オブジェクトをメンバーとして、プレーンなJavaScriptオブジェクトをループさせる方法
-
[解決済み] テキストボックスのEnterキーで、JavaScriptでボタンクリックをトリガーする
-
[解決済み] AngularJSを使用して、ブラウザのコンソールで$scope変数にアクセスするにはどうすればよいですか?
-
[解決済み] Node.jsでbase64エンコードされた画像をAmazon S3へアップロードする
-
[解決済み] JSXとLoadshを使用して、ある要素をn回繰り返す方法
-
[解決済み] AJAX Mailchimp サインアップフォームの統合
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] 検索エンジンはAngularJSアプリケーションをどのように扱っているのか?
-
[解決済み] <Enter>でjQuery UIダイアログを送信する
-
[解決済み] React js 親コンポーネントから子コンポーネントの状態を変更する
-
[解決済み] Javascript 空の配列の削減
-
[解決済み] Javascript / jQueryでAndroid端末を検出する。
-
[解決済み] ECMAScriptとは?
-
[解決済み] JavaScriptで長い配列を小さい配列に分割する方法
-
[解決済み] JavaScriptのArray.sort()メソッドでシャッフルするのは正しいのか?
-
[解決済み] V8 Javascript エンジンのスタンドアロン実行
-
[解決済み] これは純関数ですか?