A Tree-View Angular Component Tale — Part 3: Reusability

Ioannes Bracciano
4 min readDec 5, 2020

--

We have been successful in our efforts so far to represent the nested structure of a tree in a flat manner and furthermore share this information efficiently down to the last node of our tree view component. To accomplish this, we introduced the Store inside the nested components, thus coupling them tightly with our specific application. In this last part of the story, we will explore ways to turn our TreeViewComponent into a stand-alone component that any application could make use of.

← Read Part 2: Nested Information

Suppose we have finished the development of Files and our tree view is really good, so good that in fact the team over at Music wants to use the same component to display organised hierarchies of artists, albums and songs. The information that Music needs to display is conceptually very different of that of Files. It does not know what a file is, neither does it include Actions like [File System] List Directory Contents. The component we have built up to now is tightly coupled to our application. In order for other applications to be able to use the component, we need to provide a general interface that others could easily consume whatever the underlying data scheme or state management system may be.

Information Flow

To decouple the component from the application State, the first thing we need to do is remove any code related to the Store from inside the component. This means that we will need to take a step back and undo some of the work we did in the previous part. Since the tree view component will not be able to access the application state directly, we need to establish a way of communication with the parent component that will be responsible for providing these kind of information down to the component when needed.

Sharing The Flat Tree Object Reference

Let’s take a moment to think about the implementation. When a user clicks on an expandable node, TreeNodeComponent needs to emit an event to notify the parent component that it has been expanded (and maybe, for a fully functional tree view, collapsed). The event may need to bubble up from the nested nodes to the tree view component and the tree view needs to re-emit it up to the parent component. The parent component will be responsible to fetch some data, if needed, and pass it down to the tree view. Up until now, we had each node receive the information it needs by selecting a specific path from the Store that corresponded to its uri. That way it could directly know and list its child nodes. Since nor the parent component nor the tree view component are able to access nested TreeNodeComponents directly, it will be impossible to pass down slices of information with that kind of precision, at least not without converting our flat representation back into a nested one. Fortunately, object references are efficient to pass down, so we can just pass the whole flat tree representation as an input to TreeViewComponent that will then be responsible to distribute the correct information down to every nestedTreeNodeComponent. Each node can then directly access the array of child nodes with its uri property.

Outputs & Event Emitters

In Angular, when we talk about information flowing from a child component to the parent, our minds go directly to the familiar Angular mechanisms, Output and EventEmitter, that allow us to pass information up the component structure. These mechanisms serve our case well enough:

As you have probably noticed, we had to write duplicate code in TreeNodeComponent and TreeViewComponent to handle the emissions. This is not a big deal when we only have one event being emitted, but soon enough, as we implement additional functionality, the need to duplicate code is going to increase. Duplicating code is not a particularly good paradigm. Nested node events also need to bubble up the component structure until they reach the tree view and, from there, get emitted to the parent component. We can tackle these problems, using another Angular mechanism: services.

Shared Service

We can create a service that will be shared across the tree view and the nested tree nodes. This way, all nested tree nodes will be able to directly send information to the tree view without the need to manually bubble up custom events. This is also a good time to create a new module for our tree view component. We will include all new files under our component directory and we will update app.module.ts to import our new component module.

Notice that we don’t have to handle the nested node events any more

Great! We reduced drastically event emissions, code duplication and we separated our component into its own module.

Custom Data Types

Lastly, we want our tree view component to not be dependent on any application defined data types. Let’s define some new types, specific for our component. First, we will create a new folder under our component directory called typings and then two new files in that directory:

We included an index.ts file as well to make importing types from other files easier. We can now start using TreeNode instead of Node. A good place to start the refactoring is the reducers:

Now the structure of every node in the state matches the interface defined in our component module and not the other way round. The rest is almost as easy as replacing the word Node with TreeNode and using FlatTreeStruct interface in places where we used to type the flat tree structure manually (note that in fs.effects.ts and fs-api.actions.ts we still need to use the old Node interface, since these files are dealing with the structure of the data coming from the server). We also took the opportunity to take out the “files” prefix from our component selector names, to better reflect that they are now in a different module on their own:

And that’s it! In a later post we will create a path input component and use it with our tree view to be able to change the root directory. Stay tuned!

--

--