Play Framework 2.1 - AngularJS routing - best solution?

I am working my way through the AngularJS tutorial. Angular uses it's own JS routing mechanism to allow for single page apps. A sample routing file for Angular looks like this:

angular.module('phonecat', []).
  config(['$routeProvider', function($routeProvider) {
  $routeProvider.
      when('/phones', {templateUrl: '/partials/phone-list',   controller: PhoneListCtrl}).
      when('/phones/:phoneId', {templateUrl: 'partials/phone-detail', controller: PhoneDetailCtrl}).
      otherwise({redirectTo: '/phones'});
}]);

I am trying to come up with a good place to store my partials (Angular specific HTML files). Ideally i WOULD like the ability to template them from within Play (i.e. have them as *.scala.html files). I can accomplish this using a a Play routes file like so:

GET     /partials/phone_index       controllers.Application.phone_index

I basically partials/ to a controller action like this:

def phone_index = Action {
  Ok(views.html.partials.phone_index())
}

The solution I am looking for is a combination of two ideals:

  1. I would have some sort of mapping that lets me visit any file under /partial/* and get back the partial file.
  2. I would like the overriding a route to a specific partial so I CAN use a controller action to dynamically fill with data (rare).

Any ideas?

Answers


When I was trying something similar I came to the conclusion that it's better to break it on 2 parts:

  • Use Play as a backend you interact with via Ajax calls
  • Store the Angular templates in Play public folder (something like /public/angular/) and use the default AngularJs way to map the templates

I know it doesn't sound great and really doesn't answer your question on how to do it, but trying to link both frameworks may be problematic due to the way templates and their urls are mapped in Angular, and the benefit will be very small as any change will imply a lot of work, thus removing the arguably main benefit of both Play and Angular, rapid development.

This also allows you to separate concerns better, which if your project grows may be important as you can just take the AngularJS code away as a standalone app connecting to a backend, and it will work fine.

You can see some sample code of what I said (based on the TODO tutorial of AngularJS) in this Github repository. I warn you, the code is not too nice, but should give you an idea and as a bonus shows you how to integrate Jasmine into Play, for AngularJS unit testing.


The eventual seed (https://github.com/angyjoe/eventual) is yet another way to build a Play + AngularJS app. The code is a sweetheart and well-documented.


This won't answer your question directly, but I found this to be the best way to build Play + Angular apps:

https://github.com/typesafehub/angular-seed-play


Yes, it's possible to create server-side meta-templates of client-side templates. This offers some unique abilities, as the two methods don't overlap completely. There's also plenty of room for confusion so be sure you know why you're writing a Play block instead of an Angular directive.

Whether or not you should do it remains an open question; it really depends on whether you actually need access to server information in your templates. An example of where I think it would be necessary and appropriate would be for implementing access control in your views.

Now to answer your question. The problem is solved by inlining the partials instead of trying to provide a route for them to be loaded on demand. See http://docs.angularjs.org/api/ng.directive:script.

Here's what the template looks like:

@(id: Long)(implicit request: RequestWithUser[AnyContent])

@import helper._

<!doctype html>
<html lang="en" ng-app="phonecat">
<head>
  <meta charset="utf-8">
  <title>Google Phone Gallery</title>
  <link rel="stylesheet" href="css/app.css">
  <link rel="stylesheet" href="css/bootstrap.css">
  <script src="lib/angular/angular.js"></script>
  <script src="js/app.js"></script>
  <script src="js/controllers.js"></script>
  <script src="js/filters.js"></script>
  <script src="js/services.js"></script>
  <script src="lib/angular/angular-resource.js"></script>
</head>
<body>
  <div ng-view></div>

  @ngTemplate("phone-list.html") {
    <div class="container-fluid">
      <div class="row-fluid">
        <div class="span12">Hello @request.user.name</div>
      </div>

      <div class="row-fluid">
        <div class="span2">
          <!--Sidebar content-->

          Search: <input ng-model="query">
          Sort by:
          <select ng-model="orderProp">
            <option value="name">Alphabetical</option>
            <option value="age">Newest</option>
          </select>

        </div>
        <div class="span10">
          <!--Body content-->

          <ul class="phones">
            <li ng-repeat="phone in phones | filter:query | orderBy:orderProp" class="thumbnail">
              <a href="#/phones/{{phone.id}}" class="thumb"><img ng-src="{{phone.imageUrl}}"></a>
              <a href="#/phones/{{phone.id}}">{{phone.name}}</a>
              <p>{{phone.snippet}}</p>
            </li>
          </ul>

        </div>
      </div>
    </div>
  }

  @ngTemplate("phone-detail.html") {
    <img ng-src="{{mainImageUrl}}" class="phone">

    <h1>{{phone.name}}</h1>

    <p>{{phone.description}}</p>

    <ul class="phone-thumbs">
      <li ng-repeat="img in phone.images">
        <img ng-src="{{img}}" ng-click="setImage(img)">
      </li>
    </ul>

    <ul class="specs">
      <li>
        <span>Availability and Networks</span>
        <dl>
          <dt>Availability</dt>
          <dd ng-repeat="availability in phone.availability">{{availability}}</dd>
        </dl>
      </li>
    </ul>
  }
</body>
</html>

And the app:

'use strict';

/* App Module */

angular.module('phonecat', ['phonecatFilters', 'phonecatServices']).
  config(['$routeProvider', function($routeProvider) {
  $routeProvider.
      when('/phones', {templateUrl: 'phone-list.html',   controller: PhoneListCtrl}).
      when('/phones/:phoneId', {templateUrl: 'phone-detail.html', controller: PhoneDetailCtrl}).
      otherwise({redirectTo: '/phones'});
}]);

Just include this helper:

@**
 * @ngTemplate
 * Generate an AngularJS inlined template.
 *
 * Note: Do not include scripts in your @template HTML. This will break the template.
 *
 * @param name
 * @param template
 *@
@(name: String)(template: Html)

<script type="text/ng-template" id="@name">@template</script>

And make sure to use it within the root scope of your angular app.


For question #1 you could introduce a route like this:

/partials/:view    controllers.Application.showView(view:String)

Then in your controller you would need to map from view name to actual view:

Map("phone_index" -> views.html.partials.phone_index())

You might want to render the templates lazy or require the request to be present, then you should probably do something like this:

val routes = Map(
  "phone_index" -> { implicit r:RequestHeader => 
     views.html.partials.phone_index()) 
  }

Your action would look something like this:

def showView(view:String) = 
  Action { implicit r =>
    routes(view)
  }

If you want a specific controller method for a certain route (question #2) you simple add a route above the dynamic one:

/partials/specific    controllers.Application.specific()

I really think it's not a very good idea even it comes from a respectfully mind indeed.

I think it's a very good practice to leave every think as defaulted (convention over configuration principle) which means for me that we've probably more interest to keep each Paradigms (Play and AngularJS) separated as one or both could evolve in the near or the far future which will have its cost for code maintenance.

The second very important point is testability, if you mix both technos you'll end up with a mix to come up with real good coverage of tests in both side of your application. Cheers


This might not answer the question exactly but you can try follow this project as it seems good example to build Play/scala/Angular apps:

https://github.com/lashford/modern-web-template#master

http://typesafe.com/activator/template/modern-web-template


Need Your Help

What are the Netty alternatives for high-performance networking?

java networking real-time netty nio

I am in the process of choosing a networking library to implement a client/server system that cannot spare any microsecond. It will implement its own protocol to send and receive messages. I am loo...

How to convert an array to object in PHP?

php arrays object casting

How can i convert an array like this to object?