I am currently rewriting the file tree in an AngularJS application. The way the file tree is implemented in the back consists of FileInfo
s, DirectoryInfo
s, and DirectoryEntry
s. Essentially files and directories can be hooked in as many directories as you like because directories just contain entries pointing to a particular file or directory ID (even to a parent ID so you can go in a circle). I would argue this is not an ideal API, but what's done is done.
The reason I even bring up the backend API is that it is completely relevant to the frontend implementation. Basically, every Node
has an ID pointing to the file or directory it corresponds to, but that alone cannot tell you where the node is in the hierarchy. Thus, when performing operations like adding/removing/renaming directory entries, you must know not only the entry name/ID but also the parent. To do this we could pass around arrays of nodes with both the parent and child (what we used to do), or keep a mapping of nodes to their parents, but both of these solutions are pretty messy. Ideally, the implementation in the frontend would be a doubly-linked graph where every directory node has a list of its children, and each of those children has a reference to the parent
I am well aware that you can call scope.$watch(expression, listener, true)
to recursively watch all of an object's properties. However, this is never called for anything remotely related to our Node
class. Yet with just the following recursive template, AngularJS begins to choke on cyclic object value
errors.
<!-- Recursive node template -->
<script type="text/ng-template" id="tree-pane_node.html">
<div style="padding-left: {{ level * 24 }}px" ng-click="$ctrl.navigateTo(node)">
<a ng-click="$ctrl.toggleOpen(node); $event.stopPropagation()">
<md-icon class="mdi mdi-24px" ng-class="node.isOpen ? 'mdi-chevron-down' : 'mdi-chevron-right'"></md-icon>
<md-icon class="mdi mdi-24px mdi-{{ node.icon }}"></md-icon>
</a>
<span>{{ node.name }}</span>
</div>
<div ng-if="node.isOpen">
<div ng-repeat="node in node.children | filter:{isDir: true} | orderBy:'name'" ng-init="level = level + 1">
<div ng-include="'tree-pane_node.html'"></div>
</div>
</div>
</script>
<!-- Kick off the recursion with the top-level (special) directories -->
<div ng-repeat="node in ::$ctrl.topLevelDirectories" ng-init="level = 0" ng-include="'tree-pane_node.html'"></div>
Note: I omitted CSS classes as well as some expression callbacks like
ng-right-click
for brevity.
There are no template bindings to node.parent
anywhere in the application. So why am I getting cyclic object value
errors? Does AngularJS deep-watching template bindings by default? If so, can I turn this off? As I said earlier, I am currently rewriting the file tree portion of the code and I know this error appears as a result of storing parent references on nodes, not due to something else in the application.
I noticed the error originates from JSON.stringify()