なみひらブログ

学んだことを日々記録する。~ since 2012/06/24 ~

AngularJSの$locationを使う時に気をつけること

背景

AngularJSでの$locationを使うときに自分の理解が間違っていていろいろハマったのでメモっときます。

前提

  • 今回はログイン画面(/login)を表示して、ログインできたらホーム画面(/home)を表示するという例。
  • ログイン処理自体は、サーバサイドのAPIAjaxで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');
    });
}]);

$locationサービスは、ブラウザアドレスバー(window.locationが基)のURLを解析し、 アプリケーションで利用可能なものにします。 アドレスバーのURLの変更は、$locationサービスに反映され、 また$locationの変更は、ブラウザのアドレスバーに反映されます。

  • 期待結果
    • アドレスバーのURLが/app/homeになる
  • 動作の結果
    • アドレスバーが変わらない(;´Д`)
  • 理由
    $scope.$on('loginSuccess', function (event, response) {
    	$location.path('/app/home');
    	$scope.$apply();
    });
}]);


$locationの動作モードには2種類ある

ブラウザのURLは変更できたが、以下のような動き。

$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
<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が変わるのみ。画面遷移しない。
  • このことも公式ドキュメントに書いてあって、以下の通り。

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^)/

まとめ

  • 巡りめぐって公式ページにちゃんと書いてある(;´Д`)

参考