AngularJS のルーティング機能を試してみた

AngularJS を使って Single Page Application を作成するなら、おそらく避けて通れないのがルーティング機能。例えば一覧と編集でビューを切り替えたい場合、ルーティング機能を使ってコントローラーとテンプレートを切り替えて描画する、はず。

先日作成したサンプルは一覧と入力が同じページに配置していたけど、AngularJS のルーティング機能を使ってこの2つを別ページに分けてみた。

まずは AngularJS を使ったコード。

<!DOCTPE html>
<html ng-app="app">
  <head>
    <meta charset="utf-8">
    <title>Angular Sample</title>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.min.js"></script>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular-resource.min.js"></script>    
    <script type="text/javascript">
    angular.module("app", ["ngResource"]).
      config(["$routeProvider", function($routeProvider) {
        $routeProvider.
          when("/", {
            // テンプレート文字列を指定
            template: '<a href="#/new">New</a>' +
                      '<ul>' +
                      '<li ng-repeat="guest in guests">{{ guest.name }}</li>' + 
                      '</ul>',
            
            // テンプレートにバインドするコントロールの名前を指定
            controller: "MainCtrl" 
          }).
          when("/new", {
            // テンプレートの URL を指定
            templateUrl: "/new.html",

            // テンプレートンバインドするコントローラーを指定
            controller: NewCtrl
          });
      }]);

    function MainCtrl($scope, $resource) {
      var Guest = $resource("/guests");
      $scope.guests = Guest.query();
    }

    function NewCtrl($scope, $resource, $location) {
      $scope.guestName = "";
      
      // API の URL を指定してモデルを作成
      var Guest = $resource("/guests");

      $scope.addGuest = function() {
        Guest.save({ name: $scope.guestName }, function(guest) {
          // 作成に成功したら一覧に戻る
          $location.path("/");
        });

        $scope.guestName = "";
      };
    }
    </script>
  </head>
  <body>
    <!--ビューを埋め込む要素は ng-view でマークする-->
    <div ng-view></div>
  </body>
</html>

ポイントは

  • angular-resource.js を読み込む
  • ルーティングの設定は $routeProvider を使って行う
  • パスと、描画するテンプレートと、バインドするコントローラーを指定する
  • テンプレートは文字列で渡してもいい
  • テンプレートの URL を指定することもできる
  • コントローラーは名前を文字列で指定することもできる

ぐらいかな。

Web APISinatra で実装しているけど、大して変わっていないので抜き出すのは省略し、今回作成したサンプルの全体を貼っておく。

# coding: utf-8
require "sinatra"
require "json"

# データはメモリ上に保存
GUESTS = []

get "/" do
  erb :home
end

# ゲスト一覧を JSON で取得する API
get "/guests" do
  content_type :json, :charset => "utf-8"
  GUESTS.to_json
end

# ゲストを追加する API
post "/guests" do
  # POST したデータを params から取得できなかったので
  # body を JSON にパース
  @guest = JSON.parse(request.body.read.to_s)
  GUESTS << @guest
  
  content_type :json, :charset => "utf-8"
  @guest.to_json
end

# 作成ページ用のテンプレートを返す
get "/new.html" do
  erb :new
end

__END__
@@ home
<!DOCTPE html>
<html ng-app="app">
  <head>
    <meta charset="utf-8">
    <title>Angular Sample</title>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.min.js"></script>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular-resource.min.js"></script>    
    <script type="text/javascript">
    angular.module("app", ["ngResource"]).
      config(["$routeProvider", function($routeProvider) {
        $routeProvider.
          when("/", {
            // テンプレート文字列を指定
            template: '<a href="#/new">New</a>' +
                      '<ul>' +
                      '<li ng-repeat="guest in guests">{{ guest.name }}</li>' + 
                      '</ul>',
            
            // テンプレートにバインドするコントロールの名前を指定
            controller: "MainCtrl" 
          }).
          when("/new", {
            // テンプレートの URL を指定
            templateUrl: "/new.html",

            // テンプレートンバインドするコントローラーを指定
            controller: NewCtrl
          });
      }]);

    function MainCtrl($scope, $resource) {
      var Guest = $resource("/guests");
      $scope.guests = Guest.query();
    }

    function NewCtrl($scope, $resource, $location) {
      $scope.guestName = "";
      
      // API の URL を指定してモデルを作成
      var Guest = $resource("/guests");

      $scope.addGuest = function() {
        Guest.save({ name: $scope.guestName }, function(guest) {
          // 作成に成功したら一覧に戻る
          $location.path("/");
        });

        $scope.guestName = "";
      };
    }
    </script>
  </head>
  <body>
    <!--ビューを埋め込む要素は ng-view でマークする-->
    <div ng-view></div>
  </body>
</html>

@@ new
<a href="#/">Back</a>
<div>
  <input type="text" ng-model="guestName" placeholder="Input name"/>
  <button ng-click="addGuest()">Add</button>
</div>

1ファイルで完結したかったので、AngularJS のルーティング機能で使うテンプレートすらもファイル内に書いて、Sinatra を経由で取得できるようにした。