$locationの使用について
$locationは何をするものですか?
$locationサービスは、ブラウザアドレスバー(window.locationが基)のURLを解析し、 アプリケーションで利用可能なものにします。 アドレスバーのURLの変更は、$locationサービスに反映され、 また$locationの変更は、ブラウザのアドレスバーに反映されます。
$locationサービスは、
- 現在のURLは、ブラウザバーに晒されているため、
- そのURLを確認することが出来ます。
- そのURLを変更することが出来ます。
-
ユーザーが下記の事を行う際に、$locationサービス自身とブラウザのURLの同期が取れるように保持します。
- ブラウザのアドレスバーの変更
- ブラウザ内の「進む」「戻る」ボタンのクリック(または、履歴リンクのクリック)
- ページ内のリンクのクリック
- メソッドのセット(protocol、host、port、path、search、hash)として、URLオブジェクトを表現します。
$locationとwindow.locationの比較
window.location | $locationサービス | |
---|---|---|
目的・用途 | 現在のブラウザの位置への読み込み/書き込みを可能にします。 | 同じです。 |
API | 直接編集可能なプロパティ付きのオブジェクトが、"生"のまま公開されています。 | jQueryスタイルのgetterとsetterが公開されています。 |
Angularアプリケーションのライフサイクルへの統合 | 無し | 全ての内部ライフサイクルのフェーズを把握していて、$watchを使用して統合… |
HTML5のAPIとのシームレスな統合 | 無し | 有り(旧ブラウザへのフォールバック付き) |
アプリケーションが読み込まれたdocroot/contextの認識 | 無し - window.location.pathは、"/docroot/actual/path"を返します。 | 有り - $location.path()は、"/actual/path"を返します。 |
いつ$locationを使用すべきか?
アプリケーションが現在のURLの変更を反映させる必要があれば、 または、ブラウザの現在のURLを変更したい場合。
何をしないのか?
ブラウザのURLが変更された際に、ページ全体のリロードを発生させません。 URLの変更後にページをリロードするために、低階層APIの$window.location.hrefを使用します。
APIの概要
$locationサービスは、インスタンス化される際に提供される設定によって、 振る舞いを変更することが可能です。 デフォルトの設定は、多くのアプリケーションに適したもので、 設定をカスタマイズをして新しい機能を有効にすることも出来ます。
$locationサービスがインスタンス化されると、 現在のブラウザのURLの取得・変更を行うjQueryスタイルのgetterとsetterメソッドを介して、 相互に作用し合う事が可能になります。
$locationサービスの設定
$locationサービスを設定するには、 $locationProviderを取得し、下記のように引数を設定します。
設定例
$locationProvider.html5Mode(true).hashPrefix('!');
getterとsetterメソッド
$locationサービスは、URLの一部(abdUrl、protocol、host、port)の読み込みのみのgetterメソッドと、 url、path、search、hashのgetter / setterメソッドを提供します。
// 現在のパスを取得
$location.path();
// パスを変更
$location.path('/newValue')
全てのsetterメソッドは、同じ$locationオブジェクトを返すのでメソッドチェーンが可能です。 例えば、複数のセグメントの変更を下記のようにsetterのチェーンで1文で書くことが出来ます。
$location.path('/newValue').search({key: value});
置換メソッド
次に、$ocationサービスがブラウザと同期された際に、最後の履歴記録に新しく作られるものを、 代わりに置き換えるべきものを$locationサービスに指示する事が出来る特別な置換メソッドが存在します。 これは、対処しなければ「戻る」ボタンの挙動が壊れる(「戻る」操作をした際に、リダイレクトが再度実行されてしまう)ような、 リダイレクトを実装したい場合に便利です。 現在のURLを新しくブラウザの履歴を作ること無く変更するには、下記のように呼び出します。
$location.path('/someNewPath');
$location.replace();
// または、これらをチェーンにすることが出来ます。
// $location.path('/someNewPath').replace();
setterは、window.location
を即座に更新しないことに注意してください。
その代わりに、$locationサービスはライフサイクルのスコープを認識し、
複数の$locationの変更を1つにとりまとめ、スコープの$digestフェーズ中にwindow.locationオブジェクトへ"commit"します。(翻訳に自信なし)
複数の$locationの状態の変更を、1つの変更としてブラウザに強要するため、
ブラウザの履歴を追加するのではなく、1度だけ全体の"commit"として置換操作を行うreplace()メソッドを呼び出すだけで十分です。
ブラウザを更新すると、$locationサービスはreplace()メソッドによってセットされたフラグをリセットするので、
replace()メソッドの再呼び出しが無ければ、今後の変更で新しい履歴が作成されてしまいます。
文字エンコードのsetter
特殊な文字を$locationサービスに渡す事が可能で、それは、 RFC3986の仕様に沿ってエンコーディングが行われます。 メソッドにアクセスする場合、
- $locationのsetterメソッドであるpath()、search()、hash()に渡される全ての値はエンコードされます。
- getter(引数無しで呼ばれるメソッド)は、後に続くpath()、search()、hash()のために、デコードされた値を返します。
- absUrl()メソッドを呼び出すと、セグメントがエンコードされた絶対URLが値として返されます。
-
url()メソッドを呼び出すと、
/path?search=a&b=c#hash
形式の、パス、検索値、ハッシュが返されます。 この各セグメントも同様にエンコードされます。
Hashbang(#)とHTML5モード
$locationサービスは、ブラウザアドレスバー内のURLの形式を制御する2つの設定モードを持ち、 Hashbangモード(デフォルト)と、HTML5のヒストリーAPIを基にしたHTML5モードがあります。 両方のモードで同じAPIが使用され、$locationサービスは適切なURLセグメントで動作し、 ブラウザAPIはブラウザのURL変更と履歴管理を容易にします。
Hashbangモード | HTML5モード | |
---|---|---|
設定 | デフォルト |
{ html5Mode: true }
|
URLフォーマット | 全てのブラウザで、HashbangのURL | モダンブラウザであれば通常のURL、旧式のブラウザであればHashbang |
<a href="">リンクの書き換え | 無し | 有り |
サーバサイドの設定の必要 | 無し | 有り |
Hashbangモード(デフォルト)
このモードでは、$locationは全てのブラウザでHashbangのURLを使用します。
例:
it('should show example', inject(
function($locationProvider) {
$locationProvider.html5Mode(false);
$locationProvider.hashPrefix('!');
},
function($location) {
// open http://example.com/base/index.html#!/a
$location.absUrl() == 'http://example.com/base/index.html#!/a'
$location.path() == '/a'
$location.path('/foo')
$location.absUrl() == 'http://example.com/base/index.html#!/foo'
$location.search() == {}
$location.search({a: 'b', c: true});
$location.absUrl() == 'http://example.com/base/index.html#!/foo?a=b&c'
$location.path('/new').search('x=y');
$location.absUrl() == 'http://example.com/base/index.html#!/new?x=y'
}
));
クローラー対策
Ajaxアプリケーションであってもインデックスされるように、 特別なメタタグをドキュメントのヘッド部に追加する必要があります。
<meta name="fragment" content="!" />
これは、クローラーボットに、_escaped_fragment_
パラメーター付きのリンクを要求するため、
サーバーがクローラーを認識し、HTMLスナップショットを提供できるようになります。
このテクニックの詳細を知りたければ、
Making AJAX Applications Crawlableを参照してください。
HTML5モード
HTML5モードでは、$locationサービスのgetterとsetterは、HTML5のヒストリーAPIを通してブラウザのURLアドレスと相互作用し、 Hashbangの代わりに通常のURLパスと検索セグメントを使用できるようにしてくれます。 もし、HTML5のヒストリーAPIがブラウザでサポートされていない場合、 $locationサービスは自動的にHashbangのURLを使用するようにフォールバックします。 これにより、ブラウザがヒストリーAPIをサポートしているかどうかを心配する必要がなくなり、 $locationサービスは自然に利用可能な最善のオプションを使用します。
- 旧式のブラウザで通常のURLを開く -> HashbangのURLへリダイレクト
- モダンブラウザでHashbangのURLを開く -> 通常のURLへ書き換え
例
it('should show example', inject(
function($locationProvider) {
$locationProvider.html5Mode(true);
$locationProvider.hashPrefix('!');
},
function($location) {
// HTML5ヒストリーをサポートしているブラウザでは、
// http://example.com/#!/aを開く
// -> http://example.com/aへ書き換え
// (http://example.com/#!/a の履歴を置換)
$location.path() == '/a'
$location.path('/foo');
$location.absUrl() == 'http://example.com/foo'
$location.search() == {}
$location.search({a: 'b', c: true});
$location.absUrl() == 'http://example.com/foo?a=b&c'
$location.path('/new').search('x=y');
$location.url() == 'new?x=y'
$location.absUrl() == 'http://example.com/new?x=y'
// html5ヒストリーがサポートされないブラウザでは、
// http://example.com/new?x=y を開く
// -> http://example.com/#!/new?x=y へリダイレクト
// (再度、http://example.com/new?x=y に履歴項目が置換される)
$location.path() == '/new'
$location.search() == {x: 'y'}
$location.path('/foo/bar');
$location.path() == '/foo/bar'
$location.url() == '/foo/bar?x=y'
$location.absUrl() == 'http://example.com/#!/foo/bar?x=y'
}
));
旧式ブラウザのためのフォールバック
HTML5のヒストリーAPIがサポートされているブラウザのために、 $locationはHTML5ヒストリーAPIを使用してパスと検索を書きます。 もし、ヒストリーAPIがブラウザでサポートされていない場合、 $locationはHasbangのURLを適用します。 これにより、ブラウザがヒストリーAPIをサポートしているかどうかを心配する必要がなくなり、 $locationサービスは自然に利用可能な最善のオプションを使用します。
Htmlリンクの書き換え
HTML5ヒストリーAPIモードを使用する場合、異なるブラウザで異なるリンクが必要になりますが、
<a href="/some?foo=bar">リンク</a>
のような、通常のURLリンクを指定するだけで問題ありません。
ユーザーがこのリンクをクリックすると、
-
旧式のブラウザでは、
/index.html#!/some?foo=bar
にURLが変更されます。 -
モダンブラウザでは、
/some?foo=bar
にURLが変更されます。
下記のようなケースでは、リンクが書き換わらない代わりに、 ブラウザは元のリンクへのページ全体の読み込みを実行します。
-
target
の要素を含むリンク
例: <a href="/ext/link?a=b" target="_self">リンク</a> -
異なるドメインへの絶対リンク
例: <a href="http://angularjs.org/">リンク</a> -
baseが定義されている際の、異なるbaseへ誘導する'/'で始まるリンク
例: <a href="/not-my-base/link">link</a>
ドメインのルート上でAngularが実行されている場合、もしかしたら同じフォルダ内にもアプリケーションのページがあり、 When running Angular in the root of a domain, along side perhaps a normal application in the same directory, the "otherwise" route handler will try to handle all the URLs, including ones that map to static files.
これを防ぐために、baseに<base href=".">をアプリケーションの
To prevent this, you can set your base href for the app to
サーバーサイド
このモードの使用は、サーバーサイド上のURL書き換えを必要とし、 基本的にアプリケーションの入り口(例: index.html)への全てのリンクの書き換えをしなければいけません。
アプリケーションへのクローリング
もし、WebクローラーにAjaxアプリケーションをインデックスして欲しい場合、 ドキュメントのHEAD部に下記のメタタグを追加する必要があります。
<meta name="fragment" content="!" />
この宣言は、クローラーに空の_escaped_fragment_
パラメーターのリンクをリクエストするため、
サーバーはクローラーを認識し、HTMLスナップショットを提供します。
このテクニックについての詳細を知りたければ、
Making AJAX Applications Crawlableを参照してください。
相対リンク
リンク、画像、スクリプト他、全ての相対リンクを確認してください。 baseのURLをメインのhtmlファイルのHEADに指定するか(<base href="/my-base">)、 全てに完全なURL(/で始まる)を使用するか、どちらかを選ばなければいけません。 なぜなら、相対URLはドキュメントの絶対URLを使用して、 アプリケーションのルートと異なる絶対URLとして解決されることがよくあるからです。
ドキュメントルートで、ヒストリーAPIが有効な状態でAngularアプリケーションを実行する際は、 全相対リンクに対して、この問題が起こらないか気を配ることを強くお勧めします。
異なるブラウザでのリンク送信
HTML5モードではリンクを書き換える機能があるため、 ユーザーは旧式のブラウザで通常のURLのリンクを開くことも、モダンブラウザでHashbangリンクを開くことも出来ます。
- モダンブラウザは、HashbangのURLを通常のURLに書き換えます。
- 旧式のブラウザは、通常のURLをHashbangのURLに書き換えます。
例
HTML5モードでは、2つの$locationインスタンスが確認で Here you can see two $location instances, both in Html5 mode, but on different browsers, so that you can see the differences. これらの$locationサービスは擬似ブラウザに繋がれています。 These $location services are connected to a fake browsers. Each input represents address bar of the browser.
Note that when you type hashbang url into first browser (or vice versa) it doesn't rewrite / redirect to regular / hashbang url, as this conversion happens only during parsing the initial URL = on page reload.
In this examples we use
<!doctype html>
<html ng-app>
<head>
<script src="http://code.angularjs.org/1.2.0/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<div ng-non-bindable class="html5-hashbang-example">
<div id="html5-mode" ng-controller="Html5Cntl">
<h4 id="hashbang-and-html5-modes_source_browser-with-history-api">Browser with History API</h4>
<div ng-address-bar browser="html5"></div><br><br>
$location.protocol() = {{$location.protocol()}}<br>
$location.host() = {{$location.host()}}<br>
$location.port() = {{$location.port()}}<br>
$location.path() = {{$location.path()}}<br>
$location.search() = {{$location.search()}}<br>
$location.hash() = {{$location.hash()}}<br>
<a href="http://www.example.com/base/first?a=b">/base/first?a=b</a> |
<a href="http://www.example.com/base/sec/ond?flag#hash">sec/ond?flag#hash</a> |
<a href="/other-base/another?search">external</a>
</div>
<div id="hashbang-mode" ng-controller="HashbangCntl">
<h4 id="hashbang-and-html5-modes_source_browser-without-history-api">Browser without History API</h4>
<div ng-address-bar browser="hashbang"></div><br><br>
$location.protocol() = {{$location.protocol()}}<br>
$location.host() = {{$location.host()}}<br>
$location.port() = {{$location.port()}}<br>
$location.path() = {{$location.path()}}<br>
$location.search() = {{$location.search()}}<br>
$location.hash() = {{$location.hash()}}<br>
<a href="http://www.example.com/base/first?a=b">/base/first?a=b</a> |
<a href="http://www.example.com/base/sec/ond?flag#hash">sec/ond?flag#hash</a> |
<a href="/other-base/another?search">external</a>
</div>
</div>
</body>
</html>
function FakeBrowser(initUrl, baseHref) {
this.onUrlChange = function(fn) {
this.urlChange = fn;
};
this.url = function() {
return initUrl;
};
this.defer = function(fn, delay) {
setTimeout(function() { fn(); }, delay || 0);
};
this.baseHref = function() {
return baseHref;
};
this.notifyWhenOutstandingRequests = angular.noop;
}
var browsers = {
html5: new FakeBrowser('http://www.example.com/base/path?a=b#h', '/base/index.html'),
hashbang: new FakeBrowser('http://www.example.com/base/index.html#!/path?a=b#h', '/base/index.html')
};
function Html5Cntl($scope, $location) {
$scope.$location = $location;
}
function HashbangCntl($scope, $location) {
$scope.$location = $location;
}
function initEnv(name) {
var root = angular.element(document.getElementById(name + '-mode'));
// We must kill a link to the injector for this element otherwise angular will
// complain that it has been bootstrapped already.
root.data('$injector', null);
angular.bootstrap(root, [function($compileProvider, $locationProvider, $provide){
$locationProvider.html5Mode(true).hashPrefix('!');
$provide.value('$browser', browsers[name]);
$provide.value('$sniffer', {history: name == 'html5'});
$compileProvider.directive('ngAddressBar', function() {
return function(scope, elm, attrs) {
var browser = browsers[attrs.browser],
input = angular.element('<input type="text" style="width: 400px">').val(browser.url()),
delay;
input.on('keypress keyup keydown', function() {
if (!delay) {
delay = setTimeout(fireUrlChange, 250);
}
});
browser.url = function(url) {
return input.val(url);
};
elm.append('Address: ').append(input);
function fireUrlChange() {
delay = null;
browser.urlChange(input.val());
}
};
});
}]);
root.on('click', function(e) {
e.stopPropagation();
});
}
initEnv('html5');
initEnv('hashbang');
<!doctype html> <html ng-app> <head> <script src="http://code.angularjs.org/1.2.0/angular.min.js"></script> <script>function FakeBrowser(initUrl, baseHref) { this.onUrlChange = function(fn) { this.urlChange = fn; }; this.url = function() { return initUrl; }; this.defer = function(fn, delay) { setTimeout(function() { fn(); }, delay || 0); }; this.baseHref = function() { return baseHref; }; this.notifyWhenOutstandingRequests = angular.noop; } var browsers = { html5: new FakeBrowser('http://www.example.com/base/path?a=b#h', '/base/index.html'), hashbang: new FakeBrowser('http://www.example.com/base/index.html#!/path?a=b#h', '/base/index.html') }; function Html5Cntl($scope, $location) { $scope.$location = $location; } function HashbangCntl($scope, $location) { $scope.$location = $location; } function initEnv(name) { var root = angular.element(document.getElementById(name + '-mode')); // We must kill a link to the injector for this element otherwise angular will // complain that it has been bootstrapped already. root.data('$injector', null); angular.bootstrap(root, [function($compileProvider, $locationProvider, $provide){ $locationProvider.html5Mode(true).hashPrefix('!'); $provide.value('$browser', browsers[name]); $provide.value('$sniffer', {history: name == 'html5'}); $compileProvider.directive('ngAddressBar', function() { return function(scope, elm, attrs) { var browser = browsers[attrs.browser], input = angular.element('<input type="text" style="width: 400px">').val(browser.url()), delay; input.on('keypress keyup keydown', function() { if (!delay) { delay = setTimeout(fireUrlChange, 250); } }); browser.url = function(url) { return input.val(url); }; elm.append('Address: ').append(input); function fireUrlChange() { delay = null; browser.urlChange(input.val()); } }; }); }]); root.on('click', function(e) { e.stopPropagation(); }); } initEnv('html5'); initEnv('hashbang'); </script> </head> <body> <div ng-non-bindable class="html5-hashbang-example"> <div id="html5-mode" ng-controller="Html5Cntl"> <h4 id="hashbang-and-html5-modes_source_browser-with-history-api">Browser with History API</h4> <div ng-address-bar browser="html5"></div><br><br> $location.protocol() = {{$location.protocol()}}<br> $location.host() = {{$location.host()}}<br> $location.port() = {{$location.port()}}<br> $location.path() = {{$location.path()}}<br> $location.search() = {{$location.search()}}<br> $location.hash() = {{$location.hash()}}<br> <a href="http://www.example.com/base/first?a=b">/base/first?a=b</a> | <a href="http://www.example.com/base/sec/ond?flag#hash">sec/ond?flag#hash</a> | <a href="/other-base/another?search">external</a> </div> <div id="hashbang-mode" ng-controller="HashbangCntl"> <h4 id="hashbang-and-html5-modes_source_browser-without-history-api">Browser without History API</h4> <div ng-address-bar browser="hashbang"></div><br><br> $location.protocol() = {{$location.protocol()}}<br> $location.host() = {{$location.host()}}<br> $location.port() = {{$location.port()}}<br> $location.path() = {{$location.path()}}<br> $location.search() = {{$location.search()}}<br> $location.hash() = {{$location.hash()}}<br> <a href="http://www.example.com/base/first?a=b">/base/first?a=b</a> | <a href="http://www.example.com/base/sec/ond?flag#hash">sec/ond?flag#hash</a> | <a href="/other-base/another?search">external</a> </div> </div> </body> </html>
注意事項
ページリロード・ナビゲーション
$locationサービスはURLの変更はさせてくれますが、ページのリロードはさせてくれません。
URLの変更、ページのリロード、異なるページへのナビゲートが必要な場合は、
低階層APIである$window.location.href
を使用してください。
スコープのライフサイクル外での$locationの使用
$locationはAngularのスコープのライフサイクルについて認識しています。 ブラウザでURLが変更されると、$locationが更新され、 $applyを呼び出して全ての$watchers/$observersに通知します。 $locationを$digestの内部で変更するのは問題ありません。 $locationはこの変更をブラウザに伝搬して、全ての$watchers/$observersに通知します。 $locationをAngularの外部(例えば、DOMイベントから、またはテスト中)から変更したい場合、 変更を伝搬するために$applyを呼びださなければいけません。
$location.path()と、"!"または"/"接頭辞
pathは常にスラッシュ(/)から始まるべきです。 $location.path()のsetterは、もし先頭にスラッシュが無ければ、それを付け加えます。
Hashbangモードの!接頭辞は、$location.path()の一部ではなく、正確にはhashの接頭辞になります。
$locationサービスのテスト
テストに$locationを使用する場合は、Angularスコープのライフサイクルの外部から行わなければいけません。 これは、scope.$apply()を使用する必要があることを意味します。
describe('serviceUnderTest', function() {
beforeEach(module(function($provide) {
$provide.factory('serviceUnderTest', function($location){
// 何かを行います...
});
});
it('should...', inject(function($location, $rootScope, serviceUnderTest) {
$location.path('/new/path');
$rootScope.$apply();
// サービスが行うべき、何かをテスト...
}));
});
前バージョンからのマイグレーション
Angularの以前のバージョンでは、$locationはパスと検索のメソッド処理にhashPath、hashSearchを使用していました。 1.2のリリースで、$locationサービスの処理はpathとsearchメソッドで処理し、 必要であればHashbangのURL(http://server.com/#!/path?search=aのような)構成情報を取得するのに使用します。
変更するコード
ナビゲーション | 変更後 |
---|---|
$location.href = value $location.hash = value $location.update(value) $location.updateHash(value) |
$location.path(path).search(search) |
$location.hashPath = path | $location.path(path) |
$location.hashSearch = search | $location.search(search) |
アプリケーション外部からの ナビゲーション |
低階層APIの使用 |
$location.href = value $location.update(value) | $window.location.href = value |
$location[protocol | host | port | path | search] | $window.location[protocol | host | port | path | search] |
読み込みアクセス | 変更後 |
$location.hashPath | $location.path() |
$location.hashSearch | $location.search() |
$location.href $location.protocol $location.host $location.port $location.hash | $location.absUrl() $location.protocol() $location.host() $location.port() $location.path() + $location.search() |
$location.path $location.search | $window.location.path $window.location.search |
$locationの双方向バインディング
Angularのコンパイラは、メソッドのための現在双方向バインディングをサポートしていません。 (Issue参照) もし、双方向バインディングが$locationオブジェクト(inputフィールド のngModelのディレクティブに使用)に必要な場合、 2つのwatcherでの$locationの更新を両方に促す拡張モデルプロパティ(例: locationPath)の使用が必要になります。(翻訳に自信なし) 例えば、
<!doctype html>
<html ng-app>
<head>
<script src="http://code.angularjs.org/1.2.0/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<div ng-controller="LocationController">
<input type="text" ng-model="locationPath" />
</div>
</body>
</html>
function LocationController($scope, $location) {
$scope.$watch('locationPath', function(path) {
$location.path(path);
});
$scope.$watch(function() {
return $location.path();
}, function(path) {
$scope.locationPath = path;
});
}
<!doctype html> <html ng-app> <head> <script src="http://code.angularjs.org/1.2.0/angular.min.js"></script> <script>function LocationController($scope, $location) { $scope.$watch('locationPath', function(path) { $location.path(path); }); $scope.$watch(function() { return $location.path(); }, function(path) { $scope.locationPath = path; }); } </script> </head> <body> <div ng-controller="LocationController"> <input type="text" ng-model="locationPath" /> </div> </body> </html>
関連API
© 2017 Google
Licensed under the Creative Commons Attribution License 3.0.
このページは、ページトップのリンク先のAngularJS公式ドキュメント内のページを翻訳した内容を基に構成されています。 下記の項目を確認し、必要に応じて公式のドキュメントをご確認ください。 もし、誤訳などの間違いを見つけましたら、 @tomofまで教えていただければ幸いです。
- AngularJSの更新頻度が高いため、元のコンテンツと比べてドキュメントの情報が古くなっている可能性があります。
- "訳注:"などの断わりを入れた上で、日本人向けの情報やより分かり易くするための追記を行っている事があります。