AngularJS のテンプレートを入れ子にしたかったから angular-ui-router を使ってみた

AngularJS でテンプレートを入れ子にしたかったけど、ng-view は入れ子にできない。 つまり AngularJS の機能だけでは困難。 おまけに ng-view は複数置いて、別々のテンプレートを表示することもできないときた。

そんな ng-view や ngRoute に置き換わるのを目指したモジュールで、angular-ui-router がある。

こいつを使えばできそう。論よりコード。

<!DOCTYPE html>
<html ng-app="UIRouterSample">
  <head>
    <meta charset="utf-8">
    <title>UIRouterSample</title>
  </head>
  <body ng-controller="MainCtrl">
    <ul>
      <li ng-repeat="label in labels">
        <a ui-sref="label({ labelName: label.name })" ng-bind="label.name"></a>
      </li>
    </ul>
    <div ui-view></div>

    <script id="mails_template" type="text/ng-template">
      <table class="table">
        <thead>
          <tr>
            <th>Title</th>
          </tr>
        </thead>
        <tbody>
          <tr ng-repeat="mail in mails">
            <td><a ui-sref=".thread({mailId:mail.id})" ng-bind="mail.title"></a></td>
          </tr>
        </tbody>
      </table>
      <div ui-view></div>
    </script>

    <script id="thread_template" type="text/ng-template">
      <table class="table">
        <thead>
          <tr>
            <th>Title</th>
            <th>Message</th>
          </tr>
        </thead>
        <tbody>
          <tr ng-repeat="mail in mails">
            <td ng-bind="mail.title"></td>
            <td ng-bind="mail.message"></td>
          </tr>
        </tbody>
      </table>
    </script>

    <script src="angular.js"></script>
    <script src="angular-ui-router.js"></script>
    <script>
      // ui.router を使う
      window.App = angular.module("UIRouterSample", ["ui.router"]);

      App.controller("MainCtrl", ["$scope", function($scope) {
        $scope.labels = [
          { id: 1, name: "today" },
          { id: 2, name: "someday" },
          { id: 3, name: "scheduled" }
        ];
      }]);

      // $stateParams からパラメータを取り出せる
      App.controller("MailsCtrl", ["$scope", "$stateParams", function($scope, $stateParams) {
        var prefix = $stateParams.labelName + "_";
        $scope.mails = [
          { id: 1, title: prefix + "aaaaaa" },
          { id: 2, title: prefix + "bbbbbb" },
          { id: 3, title: prefix + "cccccc" }
        ];
      }]);
      App.controller("ThreadCtrl", ["$scope", "$stateParams", function($scope, $stateParams) {
          var prefix = "mail" + $stateParams.mailId + "_";
          $scope.mails = [
            { id: 1, title: "aaaaaa", message: prefix + "hogehoge" },
            { id: 2, title: "bbbbbb", message: prefix + "fugafuga" },
            { id: 3, title: "cccccc", message: prefix + "foobar" }
          ];
      }]);

      // ステートを登録
      // URL・コントローラー・テンプレートのセットに名前を付ける
      App.config(["$stateProvider", "$urlRouterProvider", function($stateProvider, $urlRouterProvider) {
        $stateProvider.
          state("label", {
            url: "/labels/{labelName:[a-zA-Z]+}",
            controller: "MailsCtrl",
            templateUrl: "mails_template"
          }).
          state("label.thread", {
            url: "/thread/{mailId:[0-9]+}",
            controller: "ThreadCtrl",
            templateUrl: "thread_template"
          });
      }]);
    </script>
  </body>
</html>

URL とコントローラーとテンプレートをセットにして「ステート」として扱っている。 ステートというより、Django のエンドポイントに近い気がする。 名前から URL を逆引きできるし。

angular-ui-router を AngularJS 本体に組み込む、なんて動きもあるみたいだけど、 早く進めて欲しいところだ。