Angular.js: Layouts and Sections

This is an old post and doesn't necessarily reflect my current thinking on a topic, and some links or images may not work. The text is preserved here for posterity.

I've been building out the new UI for Octopus 2.0 in Angular.js, which has been a fun process and something I hope to blog more about later.

Angular layouts/master pages and sections

Coming from an ASP.NET MVC background, one of the concepts I found myself missing was that of "sections". A solution I came up with was to use two custom directives:

  • octo-placeholder designates an area on the parent layout where content will be placed
  • octo-section designates content that will be added to the parent layout

My layout page looks like this (simplified):

<... snip ...>
<body>
  <div class='top'>
    <div class='tools' octo-placeholder='tools'></div>

    <div class='breadcrumbs' octo-placeholder='breadcrumbs'></div>
  </div>

  <ng-view></ng-view>
</body>

My view looks like this:

<script octo-section="breadcrumbs" type="text/ng-template" >
  <p>This is the breadcrumbs area {{ someVariable }}</p>
</script>

<script octo-section="tools" type="text/ng-template" >
  <a ng-show="loaded">Hello</a>
</script>

<p>This is the main content</p>

The directives that make this all work are:

.directive("octoPlaceholder", function(octoUtil, $compile, $route, $rootScope) {
  return { 
    restrict: 'AC',
    link: function(scope, element, attr) {
      // Store the placeholder element for later use
      $rootScope["placeholder_" + attr.octoPlaceholder] = element[0];

      // Clear the placeholder when navigating
      $rootScope.$on('$routeChangeSuccess', function(e, a, b) {
        element.html('');
      });
    }
  };
})

.directive("octoSection", function(octoUtil, $compile, $route, $rootScope) {
  return {
    restrict: 'AC',
    link: function(scope, element, attr) {
      // Locate the placeholder element
      var targetElement = $rootScope["placeholder_" + attr.octoSection];

      // Compile the template and bind it to the current scope, and inject it into the placeholder
      $(targetElement).html($compile(element.html())(scope));
    }
  };
})

Unlike solutions that use ng-include, the directives use the current view's scope rather than creating a new scope. This means that you can use bindings within the sections without a problem.

Hopefully this helps someone else!