Spring BootとAngularUI UI-Routerを使ってみる


# はじめに

Spring BootとAngularUI UI-Routerを使った、最も簡単なWebアプリケーションを作成します。

## 参考

Spring Security and Angular JS
https://spring.io/guides/tutorials/spring-security-and-angular-js/

In-Depth Guide
https://github.com/angular-ui/ui-router/wiki

# 前提条件

下の記事の後の状態を前提にしています
[AngularJSのControllerから、SpringのRestControllerにアクセスしてみる](http://qiita.com/quwahara/items/80ba4771c56d913aeea6)


# 環境

JVM:          1.8.0_45 (Oracle Corporation 25.45-b02)
OS:           Mac OS X 10.11.3 x86_64

## Spring Tool Suite

Version: 3.7.3.RELEASE
Build Id: 201602250940
Platform: Eclipse Mars.2 (4.5.2)
(こちらから入れました https://spring.io/tools/sts/all)

Buildship: Eclipse Plug-ins for Gradle	1.0.13.v20160411-1723
(Help -> Eclipse marketplaceから入れました)

# 手順

## UI-Routerを追加する

UI-Routerが使えるように、ビルドスクリプトの依存物として、// Addを追記します。

```groovy:build.gradle
dependencies {
	compile 'org.webjars:jquery:2.2.3'
	compile 'org.webjars:angularjs:1.5.3'
	// Add
	compile 'org.webjars:angular-ui-router:0.2.18'
	compile 'org.webjars:bootstrap:3.3.6'

	compile('org.springframework.boot:spring-boot-starter-web')
	testCompile('org.springframework.boot:spring-boot-starter-test') 
}
```

### 参考

WebJars http://www.webjars.org/

追記を反映するために、Package ExplorerでSSP37(プロジェクトルート)を選び右クリック、
Gradle -> Refresh Gradle Project
をクリックします。
Package ExplorerのProject and External Dependenciesに、追記したJarあるか確認します。

## index.htmlを書き換える

コンテンツが変えられるように、index.htmlを書き換えます。
とりあえずコンテンツの切替が分かるように、blueとgreenというコンテンツを追加しました。
コンテンツを切替えるためのリンクを\<ul\>の下に追記しました。
\<a\>の[ui-sref](http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.directive:ui-sref)は、state名を指定すると自動的にhref属性を準備してくれる、UI-Routerの便利なディレクティブです。
コンテンツ表示先としてui-viewディレクティブを指定した\<div\>を追記しました。
先ほどの\<a\>をクリックすると、この\<div\>中身が、後で作成するhtmlのTemplateに書き換えられます。
UI-Routerを参照する\<script\>も追記します。

```html:src/main/resources/static/index.html
<html ng-app="ssp37App">
<head>
<meta charset="utf-8">
<title>SSP37</title>
<link rel="stylesheet" href="webjars/bootstrap/3.3.6/css/bootstrap.min.css">
</head>
<body>
	<!-- Change -->
	<div class="container">
		<ul class="nav nav-pills" role="tablist">
			<li><a ui-sref="hello"	>hello</a></li>
			<li><a ui-sref="blue"  	>blue</a></li>
			<li><a ui-sref="green"	>green</a></li>
		</ul>
	</div>
	<!-- Add -->
	<div ui-view class="container"></div>
	<script src="webjars/jquery/2.2.3/jquery.min.js"></script>
	<script src="webjars/bootstrap/3.3.6/js/bootstrap.min.js"></script>
	<script src="webjars/angularjs/1.5.3/angular.min.js"></script>
	<!-- Add -->
	<script src="webjars/angular-ui-router/0.2.18/angular-ui-router.min.js"></script>
	<script src="js/app.js"></script>
</body>
</html>
```

## app.jsを書き換える

UI-Routerによってコンテンツが変わるように、app.jsへstateやcontrollerを指定します。

詳しくはUI-RouterのDocumentsの下を参考にして下さい。

In-Depth Guide
https://github.com/angular-ui/ui-router/wiki

```js:src/main/resources/static/js/app.js
angular.module('ssp37App', ['ui.router'])
    .config(function($stateProvider, $urlRouterProvider) {
        $urlRouterProvider.otherwise("/hello")
        $stateProvider
            .state('hello', {
                url: "/hello",
                templateUrl: "templates/hello.html",
                controller: "HelloCtrl"
            })
            .state('blue', {
                url: "/blue",
                templateUrl: "templates/blue.html",
                controller: "BlueCtrl"
            })
            .state('green', {
                url: "/green",
                templateUrl: "templates/green.html",
                controller: "GreenCtrl"
            })
            ;
    })
    .controller('HelloCtrl', function($http, $scope) {
        $http.get('/helloOrBad?bad=false')
        .then(function(response) {
             $scope.hello = response.data;
         })
         .catch(function(response) {
             $scope.status = response.status;
             $scope.statusText = response.statusText;
         })
    })
    .controller('BlueCtrl', function($scope) {
        $scope.color = "blue"
    })
    .controller('GreenCtrl', function($scope) {
        $scope.color = "green"
    })
    ;
```

## Templateを導入する

Templateを使ってViewを表示します。
src/main/resources/staticの下にtemplatesディレクトリを作り、
その下に、helloとblue、greenのTemplateを新規作成します。
stateに指定している、templateUrlに対応しています。

```html:src/main/resources/static/templates/hello.html
<h1>Check Controller</h1>
<p>The ID is {{hello.id}}</p>
<p>The text is {{hello.text}}</p>
<p>The status is {{status}}</p>
<p>The statusText is {{statusText}}</p>
```

```html:src/main/resources/static/templates/blue.html
<p>{{color}} sky</p>
```

```html:src/main/resources/static/templates/green.html
<p>{{color}} land</p>
```

## 動作確認する

http://localhost:8080/index.html を開き、\<ul\>の下に置いたリンクの、hello、blue、greenをクリックして、コンテンツが変わるのを確認します。

## RestControllerへのアクセスをfactoryにする

HelloCtrlで直接アクセスしていた/helloOrBadを、factoryに括りだし、HelloCtrlに注入するように変更します。
ブラウザを再読み込みして、動作確認をします。

```js:src/main/resources/static/js/app.js
angular.module('ssp37App', ['ui.router'])
    // Omit
    // Add
    .factory('hello', function($http) {
        return {
            get: function() {
                return $http.get('/helloOrBad?bad=false');
            }
        };
    })
    // Change
    .controller('HelloCtrl', function($scope, hello) {
        hello.get()
        .then(function(response) {
            $scope.hello = response.data
        })
        .catch(function(response) {
            $scope.status = response.status;
            $scope.statusText = response.statusText;
        })
        ;
    })
    // Omit
    ;
```

## stateのresolveを使ってみる

stateのresolveを導入してみます。
stateのresolveで、先ほどのfactoryにしたhelloを利用し、その結果をHelloCtrlに注入するように変更します。
ブラウザを再読み込みして、動作確認をします。


```js:src/main/resources/static/js/app.js
angular.module('ssp37App', ['ui.router'])
    .config(function($stateProvider, $urlRouterProvider) {
        $urlRouterProvider.otherwise("/hello")
        $stateProvider
            .state('hello', {
                url: "/hello",
                templateUrl: "templates/hello.html",
                controller: "HelloCtrl",
                // Add
                resolve: {
                    helloRsl: function(hello) {
                        return hello.get();
                    } 
                }
            })
            .state('blue', {
                url: "/blue",
                templateUrl: "templates/blue.html",
                controller: "BlueCtrl"
            })
            .state('green', {
                url: "/green",
                templateUrl: "templates/green.html",
                controller: "GreenCtrl"
            })
            ;
    })
    .factory('hello', function($http) {
        return {
            get: function() {
                return $http.get('/helloOrBad?bad=false');
            }
        };
    })
    // Change
    .controller('HelloCtrl', function($scope, helloRsl) {
        $scope.hello = helloRsl.data
    })
    .controller('BlueCtrl', function($scope) {
        $scope.color = "blue"
    })
    .controller('GreenCtrl', function($scope) {
        $scope.color = "green"
    })
    ;
```

先ほどのhello factoryをcontrollerに、注入するのと、resolveを介して注入するのには、違いがあります。
hello factoryの場合は\$http.getが解決する前に、controllerがインスタンス化されますが、resolveの場合は\$http.getが解決したあとに、インスタンス化されます。
resolveでは例のhelloRslようにキーを指定して、\$http.getのようなpromiseを返す関数を複数指定できます。それらの全てが解決したところで、controllerの中を実行するような処理が行えます。
例えば、2つのREST APIにアクセスして、応答が両方揃ったときにcontrollerを実行したいようなときに便利です。

## 参考

Sample Repository
https://github.com/quwahara/SSP37/tree/410-ui-router