最近、ページ内リンクというのはあまり使われないと思いますが、使いたいケースがあったので、調べました。備忘メモです。
ページ内リンクというのは、リンク先に
<a name="id001">
とか
<div id="id001">
とか書いておいて、リンク元を
<a href="#id001">
とすることで、このリンクをクリックするとリンク先にスクロールしてくれるものです。
ところが、AngularJSではこれはルートとみなされ、ご丁寧に#/id001
に変換して、$routeProviderの処理がされてしまいます。当然、そのようなルートは用意していないので、予期せぬ動作になります。例えば、otherwiseにredirectToを設定していたりすると、トップページに戻ります。
これは困ったということで、いろいろ調べました。はじめに見つけたのがこれ。
Angular JS - Scrolling To An Element By Id
AngularJSには$anchorScroll()
というのが用意されていてその使い方が説明されています。具体的には、$rootScope
の$routeChangeSuccess
イベントをフックするのですが、詳しくは、上記サイトをご覧ください。
さて、これを試しましたが、うまくありませんでした。
新しいページを表示してスクロールする分にはいいのですが、同一ページでもリフレッシュされてしまいます。つまり、$routeChangeSccess
イベントはルートが新しくなってから呼ばれるものなのです。AngularJSのドキュメントを読むと、$routeChangeStart
というイベントもあるのですが、結局、そのイベント処理のあとに、ルートを書き換えてしまいました。
さらに調べると、次のIssueを見つけました。
https://github.com/angular/angular.js/issues/1699
$location
のhashやpathを変更しても、ルートを書き換えない方法を用意してほしいという要望です。この中に書いてある方法では解決しませんでしたが、最後にPRを見つけました。
https://github.com/angular/angular.js/pull/2555
蛇足ですが、このPRは分かりやすく書かれてますね、Basic Usageとか。 でも、結局このPRもリジェクトされてました。 これを許すと、URLの状態とルートの状態が整合しないことができてしまうので避けたいそうです。
悩んだあげく、次の方法で解決しました。
まず、$routeProviderを次のようにします。
$routeProvider.when('/home', {
templateUrl: 'partials/home.html',
controller: HomeCtrl,
reloadOnSearch: false
}).
otherwise({
redirectTo: '/home'
});
ポイントは、reloadOnSearchです。OnSearchとありますが、hashの変更にも有効のようでした。
その上で、
<a href="#id001">
の代わりに、
<a href="#/home#id001">
とします。まあなんとも普通の方法ですが、これで目的は達成できました。
/home
がハードコードされているのが気になる方は、$location
が使える状況なら、次のようにしましょう。
'<a href="#' + $location.path() + '#id001">'
いかがでしょうか。AngularJSを使いつつ、レガシーなことをやろうとすると大変だ、という例でした。
ちなみに、hrefを使わなくて良いのなら、
Angular JS - Scrolling To An Element By Id
の2つ目の方法がよさそうです。試していませんが。
コメント