May 4, 2014

AngularJS recursive templates

Let’s say we have a hierarchical set of data such as a list of directories or product categories in an e-commerce site.

How can we use AngularJS to display a tree, regardless of its depth?

The answer is recursive templates using Angular’s ng-include directive.

From the following JavaScript array:

$scope.categories = [
  { 
    title: 'Computers',
    categories: [
      {
        title: 'Laptops',
        categories: [
          {
            title: 'Ultrabooks'
          },
          {
            title: 'Macbooks'            
          }
        ]
      },
      {
        title: 'Desktops'
      },
      {
        title: 'Tablets',
        categories: [
          { 
            title: 'Apple'
          },
          {
            title: 'Android'
          }
        ]        
      }
    ]
  },
  {
    title: 'Printers'
  }
];

The goal is to render out a nested list like so:

  • Computers
    • Laptops
      • Ultrabooks
      • Macbooks
    • Desktops
    • Tablets
      • Apple
      • Android
  • Printers

We’ll start by displaying the top level categories:

<ul>
    <li ng-repeat="category in categories">{{ category.title }}</li>
</ul>

This gives us:

  • Computers
  • Printers

If we want to render the next level of categories we need to update the HTML:

<ul>
    <li ng-repeat="category in categories">
        {{ category.title }}
        <ul ng-if="category.categories">
            <li ng-repeat="category in category.categories">
                {{ category.title }}
            </li>
        </ul>
    </li>
</ul>

Now we get:

  • Computers
    • Laptops
    • Desktops
    • Tablets
  • Printers

We could continue duplicating the HTML for each level of sub-categories but aside from being very cumbersome, this would only work if you knew the maximum depth of the tree in advance.

A much better solution is to use a template and render it recursively for each level in the tree. First we’ll define an inline template:

<script type="text/ng-template" id="categoryTree">
    {{ category.title }}
    <ul ng-if="category.categories">
        <li ng-repeat="category in category.categories" ng-include="'categoryTree'">           
        </li>
    </ul>
</script>

For each category we display the title and then a nested list of its sub-categories by rendering the same template using ng-include.

The final task is to kick of the recursive template with our root categories:

<ul>
    <li ng-repeat="category in categories" ng-include="'categoryTree'"></li>
</ul>  

You can see the working demo on JSFiddle

How it works

ng-repeat creates a new child scope for each iteration. The category variable we reference inside the template is that of the current iteration. The recursive template works because we use the same “category” variable name both inside and outside the template. If you have different variable names you could make use of ng-init:

<li ng-repeat="parentCategory in categories" 
    ng-include="'categoryTree'" 
    ng-init="category=parentCategory"></li> 

© 2022 Ben Foster