var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope) {
  $scope.root = {
    type: "dir",
    filename: "root",
    children: []
  };
  
  $scope.generateTree = function () {
    $scope.root.children.length = 0;
    populateChildren($scope.root, 60);
  };
  
  function populateChildren (node, n) {
    var t = n / 3;
    var c = n;
    
    while (n >= 0) {
      var r = 1 + (Math.random() * t) | 0;
      
      if (r > t / 2) {
        var dir = {
          type: "dir",
          filename: "dirname" + r,
          children: [],
        };
        
        node.children.push(dir);
        
        populateChildren(dir, n - r);
      } else {
        node.children.push({
          type: "file",
          filename: "filename" + r + ".ext",
        });
      }
      
      n -= r;
    }
  }
  
});


app.directive("treeNode", ["$compile", "$timeout", function ($compile, $timeout) {
  return {
    restrict: "E",
    replace: true,
    require: "treeNode",
    template: "<div></div>",
    controller: "TreeNodeController",
    controllerAs: "treeNode",
    scope: {},
    bindToController: {
      depth: "=",
      node: "=",
    },
    link: function ($scope, $element, $attrs, treeNode) {
      var template = '<tree-{{type}} node="treeNode.node" depth="1 + treeNode.depth"></tree-{{type}}>'
        .replace("{{type}}", treeNode.node.type);
      var replacement = angular.element(template);
      
      $element.replaceWith(replacement);
      
      // BLOCK THE BROWSER
      // $timeout(function () {
        $compile(replacement)($scope);
      // }, 0, false);
    }
  };
}]);

app.controller("TreeNodeController", [ function () {
  this.depth = this.depth || 0;
}]);

app.directive("treeDir", [function () {
  return {
    restrict: "E",
    replace: true,
    templateUrl: "treeDir.html",
    controller: "TreeDirController",
    controllerAs: "treeDir",
    scope: {},
    bindToController: {
      depth: "=",
      node: "=",
    }
  };
}]);

app.controller("TreeDirController", [ function () {
  this.depth = this.depth || 0;
}]);

app.directive("treeFile", [function () {
  return {
    restrict: "E",
    replace: true,
    templateUrl: "treeFile.html",
    controller: "TreeFileController",
    controllerAs: "treeFile",
    scope: {},
    bindToController: {
      depth: "=",
      node: "=",
    }
  };
}]);

app.controller("TreeFileController", [ function () {
}]);
<!DOCTYPE html>
<html ng-app="plunker">

  <head>
    <meta charset="utf-8" />
    <title>AngularJS Plunker</title>
    <link data-require="bootstrap-css@*" data-semver="3.3.1" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" />
    <script>document.write('<base href="' + document.location + '" />');</script>
    <link rel="stylesheet" href="style.css" />
    <script data-require="angular.js@1.4.x" src="https://code.angularjs.org/1.4.0-beta.6/angular.js" data-semver="1.4.0-beta.6"></script>
    <script src="app.js"></script>
  </head>

  <body class="container" ng-controller="MainCtrl">
    <div class="row">
      <div class="col-xs-12" ng-include="'README.html'"></div>
    </div>
    <div class="row">
      <div class="col-xs-12">
        <p>
          <button class="btn btn-large btn-primary" ng-click="generateTree()">(Re)generate tree</button>
        </p>
      </div>
    </div>
    <div class="row">
      <div class="col-xs-12">
        <tree-dir node="root"></tree-dir>
      </div>
    </div>
  </body>

</html>
/* Put your css in here */

.tree-filename {
  &:hover {
    background-color: #f5f5f5;
  }
}
<div class="tree-dir">
  <div class="tree-filename" ng-bind="treeDir.node.filename" ng-style="{paddingLeft: treeDir.depth * 20 + 'px'}"></div>
  <div class="tree-children">
    <tree-node node="node" depth="treeDir.depth" ng-repeat="node in treeDir.node.children"></tree-node>
  </div>
</div>
<div class="tree-file">
  <div class="tree-filename" ng-bind="treeFile.node.filename" ng-style="{paddingLeft: treeFile.depth * 20 + 'px'}"></div>
</div>
# Degenerate performance case for `$compile`

In Plunker.NEXT, I'm adding support for 'real' file trees with directories
and files. This means the sidebar will have its very own file **tree**. No more
map of `{filename: 'content'}`.

> Welcome to 2015 @filearts

1. Anyway, rendering file trees in Angular poses an interesting problem. The
natural approach would be to have two directives: `tree-dir` and `tree-file`.
Given the recursive nature of `tree-dir`, this is a guaranteed way to blow the
stack when `$compile` tries to keep expanding forever.

2. The 'solution' is to have a third directive, `tree-node` that, on link, will
generate either `tree-dir` or `tree-file` markup and then `$compile` itself.

3. This works, but it means that to render a very deep tree, the successive calls
to `$compile` will create an extemely long, thread-blocking `$digest` cycle.

4. I broke the cycle by introducing artificial `$timeout`s, but know there must
be a better way

**What alternate approaches are there to optimize this type of structure that do
not involve going 'outside' the typical Angular design principals?**

Click the button to run a test-case.