Embedded AngularJS partials, or: HTML5 !== XHTML

I just bumped heads with a really gnarly problem. In fact it’s a miracle that I stumbled upon the solution, it could have gone unseen for a long time!

The root of the problem was that I used XHTML syntax in a HTML5 document, and I didn’t get what I expected. I’m still getting used to HTML5 and my understanding was that HTML5 was a superset of HTML4 and XHTML with some extensions. As such I expected XHTML syntax to be supported by HTML5 — but, not so!

I was trying to refactor my AngularJS app to use partial views that were embedded in <script> tags with the type ‘text/ng-template’ and an associated ID.

On my way I found this article on StackOverflow which said:

make sure that the inline script tags are children of the element that has the ng-app=”myApp” attribute.

That was actually a problem I had to solve, I think, because I’d put the ng-app directive on the <html> element not the <body> element. But things still weren’t working and my code looked like this:

...
  <body ng-app="fflurry_app" ng-controller="MainCtrl">
    <ul class="menu">
      <li><a href="#/home">Home</a></li>
    </ul>
    <div ng-view />
    <!-- partials: -->
<script type="text/ng-template" id="home.html">
<h1>Welcome!</h1>
</script>
<script type="application/javascript">
'use strict';
var fflurry_app = angular.module( 'fflurry_app', [] ).
  config( [ '$routeProvider', function( $routeProvider ) {
      $routeProvider.when( '/home', { templateUrl: 'home.html', controller: 'MainCtrl' } );
      // ...      
    }]);
    // ...
</script>
...
</body></html>

So… have you spotted the problem? It’s a tricky one… the problem is with the ‘<div ng-view />’ element. This is being parsed as an open <div> with no closing </div> in HTML5. In XHTML this would be an opened and closed element. In HTML5 it’s ignoring the ‘/>’ and just opening a <div>! Hah! So the <script> partials are children on this <div> and not of the ng-app container, so they’re not being found by Angular!

The solution is now trivial. Just make sure we open and close our ng-view <div> like this: <div ng-view></div>.