AngularJSの$locationを使う時に気をつけること
背景
AngularJSでの$locationを使うときに自分の理解が間違っていていろいろハマったのでメモっときます。前提
- 今回はログイン画面(/login)を表示して、ログインできたらホーム画面(/home)を表示するという例。
- ログイン処理自体は、サーバサイドのAPIにAjaxでPOST(JSON)して認証処理をします。
- APIを叩くサービスは以下の通り。
- ajaxでリクエストを投げて、結果をブロードキャストする
app.service('authService', [ '$rootScope', function($rootScope) { this.login = function(user) { $.ajax({ type : 'POST', url : '/app/api/login', contentType : 'application/json', data : JSON.stringify(user), dataType : 'json', success : function(json) { $rootScope.$broadcast('loginSuccess', json) }, error : function(json) { $rootScope.$broadcast('loginError', json) } }); } }]);
- 画面(フォーム)は以下の通り。
- 後述するloginControllerと紐付けを行う。
<form class="form-inline" ng-controller="loginController" ng-submit="login()"> <input type="text" class="form-control" ng-model="userId"> <input type="password" class="form-control" ng-model="password"> <button type="submit" class="btn btn-default" ng-disabled="disabled">Login</button> </form>
メモ
broadcast経由で$locationに対して処理を行う際は、$scope.$apply()をする必要がある
- 最初に実装したのは以下のような感じ。
app.controller('loginController', [ '$scope', '$location', 'authService', function($scope, $location, authService) { $scope.login = function() { $scope.disabled = true; var user = { userId : $scope.userId, password : $scope.password } authService.login(user); }; $scope.$on('loginSuccess', function (event, response) { $location.path('/app/home'); }); }]);
- $localtionについては以下に概略が書いてある。
$locationサービスは、ブラウザアドレスバー(window.locationが基)のURLを解析し、 アプリケーションで利用可能なものにします。 アドレスバーのURLの変更は、$locationサービスに反映され、 また$locationの変更は、ブラウザのアドレスバーに反映されます。
- 期待結果
- アドレスバーのURLが/app/homeになる
- 動作の結果
- アドレスバーが変わらない(;´Д`)
- 理由
- AngulerJS管理外のイベントハンドラでプロパティを変更しても、更新処理が走らないらしい
- その場合は「$scope.$apply()」を明示的に記載して、強制的に反映させる必要がある。
$scope.$on('loginSuccess', function (event, response) { $location.path('/app/home'); $scope.$apply(); }); }]);
$locationの動作モードには2種類ある
ブラウザのURLは変更できたが、以下のような動き。- 期待結果
- 「/app/home」となる。
- 結果
- 「/app/login#/app/home」となった(;´Д`)
- これもドキュメントを読むとちゃんと書いてあった(;´Д`)デフォルトはHashbangモードというもので動くらしい。
$locationサービスは、ブラウザアドレスバー内のURLの形式を制御する2つの設定モードを持ち、 Hashbangモード(デフォルト)と、HTML5のヒストリーAPIを基にしたHTML5モードがあります。 両方のモードで同じAPIが使用され、$locationサービスは適切なURLセグメントで動作し、 ブラウザAPIはブラウザのURL変更と履歴管理を容易にします。
- 今回はHTML5モードで動かしたいので、設定を追加した。
app.config(['$locationProvider', function($locationProvider) { $locationProvider.html5Mode(true).hashPrefix('!'); }]);
- これで動かしたら、なんかエラーでた(;´Д`)
Error: [$location:nobase] http://errors.angularjs.org/1.4.7/$location/nobase I/<@https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js:6:416 hf/this.$get<@https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js:103:291 e@https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js:39:191 fb/t.$injector<@https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js:41:8 d@https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js:38:394 e@https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js:39:161 Xe/this.$get</</<@https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js:80:205 K@https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js:61:190 g@https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js:54:410
- 調べると公式ページに書いてあって、ベースとなるパスを指定する必要があるらしい。
- https://code.angularjs.org/1.4.7/docs/error/$location/nobase
- htmlに以下のように書くか、locationProviderにbaseは不要だと設定する
<head> <base href="/"> ... </head>
か
app.config(['$locationProvider', function($locationProvider) { $locationProvider.html5Mode({ enabled: true, requireBase: false }); }]);
画面遷移をしたい場合は$locationではなく、$window.location.hrefを使う
ブラウザのURLは正しく変更できたが、アドレスバーのURLが変わったのみで画面遷移しない。※結局、自分がやりたいことをするためには「$location」ではなく「$window.location.href」を使うべきだと気づく┐(´д`)┌
- 期待動作
- URLを変更したので、画面遷移が行われる。
- 実際の動作
- ブラウザのアドレスバーのURLが変わるのみ。画面遷移しない。
- このことも公式ドキュメントに書いてあって、以下の通り。
- $locationは、ブラウザURLを変更するのみ。
- $locationは、本来はシングルページ的なURLの状態遷移のために使うイメージ(≠画面遷移)。
- https://code.angularjs.org/1.4.7/docs/guide/$location
It does not cause a full page reload when the browser URL is changed. To reload the page after changing the URL, use the lower-level API, $window.location.href.
- 今回は画面遷移したいので、$window.location.hrefを使う
- $windowをDIして使う。
app.controller('loginController', [ '$scope', '$window', 'authService', function($scope, $window, authService) { $scope.login = function() { $scope.disabled = true; var user = { userId : $scope.userId, password : $scope.password } authService.login(user); }; $scope.$on('loginSuccess', function (event, response) { $window.location.href = '/app/home'; }); }]);
意図した動き(ログイン処理後に画面遷移)になった\(^o^)/
まとめ
- 巡りめぐって公式ページにちゃんと書いてある(;´Д`)
参考
- https://docs.angularjs.org/api
- AngularJS: how to enable $locationProvider.html5Mode with deeplinking - Stack Overflow
- How to config routeProvider and locationProvider in angularJS? - Stack Overflow
- AngularJS の $locationProvider.html5Mode について - Qiita
- AngularJSの$locationサービスを使ってURLを操作 | VPSサーバーでWebサイト公開 備忘録 ~Linux、MySQLからAJAXまで
- 作者: 池添明宏,金井健一,吉田徹生,丸山弘詩
- 出版社/メーカー: インプレス
- 発売日: 2014/09/05
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る