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.