In this tutorial, we will learn how to create a file-explorer in angular.
Why? Because we can!
And it’s fun!
We will explore how to use the angular material library to create a visual appealing component to manage our files.
Also, we take a look at how dumb components can help us re-using our components and keep them independent from the actual file-api of the backend.
Ready?
Let’s get started!
Setting up a new Project
First of all, we need to create a new angular project. In this tutorial, we are going to use the angular-cli for that.
With the cli, you can create a new project by using this command:
ng new angular-file-explorer
To tidy things up, we will create our file-explorer-component in a separate feature-module.
To create this module, use the following command at the root of our new project.
To use the library, we have to install it first. We do so by using npm with this command:
npm install --save @angular/material @angular/cdk
To import the styles that come along with the library, we need to import them into our style.css file:
We are also going to need the material design icons, so we add a link in our index.html file:
To align items nicely, we are also using flexbox:
npm install --save @angular/flex-layout
Furthermore, we are going to need a small module to generate (pseudo)random ids.
npm install --save uuid
Importing Stuff
Because angular material follows a modular approach, we need to import a lot of stuff into our file-explorer-module before we can start. So let’s get that out of the way right now!
Basically what we have to do, is to adjust the file-explorer-module until it looks like this:
Everything is a FileElement
The next thing we should do is to define a model for our files and folders. For that, we are going to create a new class inside of the file-explorer module.
ng generate class file-explorer/model/fileElement
We will define everything that is displayed in our explorer using this FileElement class. Independent of whether it is a file or a folder.
The class is very basic. All it got is a name for the file/folder, an (optional) id, a parent folder and a flag whether it is a folder or a file.
Creating a Dumb Component
One of the main goals of this guide, besides creating a functioning file-explorer of course, is to make the file-explorer-component as re-usable as possible. Because there are so many ways to handle files in the backend, I decided to make the component itself as dumb as possible.
That way, it is completely independent of any server-logic. Also, it will have zero private states. That way it is possible to use this component with state-management libraries like ngrx.
To achieve that, everything is handled via @Inputs and @Outputs. All the component does is receive an array of FileElements and display them. Once the user takes action, the component fires an event to notify the parent component.
By doing it this way, we also get a nice separation between view and logic.
Here is how the component looks like with all the @Inputs and @Outputs defined:
Implementing the Component Logic: Oh wait, there is none!
All of these @Outputs need to be easily accessible in our component-template, as well.
To get that, we will implement tiny functions to emit a new event for each @Output. These functions are all placed inside of the FileExplorerComponent.
You will notice that there are three methods that are not implemented yet.
Two of them are to open separate dialogues, to either rename or create a new folder.
The third will be responsible for opening the context (right-click) menu for each file/folder.
We will get to that. Let’s take a look at how to create the dialogues first!
Angular Material Dialogues
Fortunately, angular material has already implemented a dialog-system for us. All we need to do is create a small component for every dialog we need.
We will need two. First, the “newFolerDialog” and second the “renameDialog”.
Although the two are almost the same, we create them as separate components, to keep them independent from each other, in case one of them needs to change.
We can use the angular-cli to create the components for us:
ng generate component file-explorer/modals/newFolderDialog
and
ng generate component file-explorer/modals/renameDialog
The templates of these dialogues look like this:
Except the title, the template of the rename dialog is just the same:
We also need to edit the .ts file of both components. Just add a field called folderName of type string to both of them.
Opening the Dialogues
Now that we have the dialogues in place, we can implement the two functions to open them in our FileExplorerComponent.
To do that, we need the dialog service from angular material. To use it in our component, we need to request it via dependency injection in the component’s constructor.
We then use this service to open each dialog. Afterward, we subscribe to its result.
Once the dialog is closed, the observable is fired and we either get the result or nothing.
If we get the result, we use that to fire either the folderAdded or elementRenamed @Output emitter.
Designing the File-Explorer
Finally, it is time to set up our fileExplorerTemplate.
Unfortunately, it is kind of hard to describe it. I’ve arranged some angular-material components, used a little bit of flexbox and voila, here is the file explorer.
No, seriously! At this point, I will assume you know how to read angular templates and just show you the code.
Because of the way how angular menus work, we also have to place the menus in the template itself. These menus are not visible until they are opened by our component-logic.
Learn more about angular material menus!
These menus are opened by the openMenu method in our fileExplorerComponent, which we have not implemented yet. So why don’t we implement it right now?
We pass that method a menu-trigger, that is placed in our template. We then use that trigger to open the menu.
I’ve also used some styles:
A Functioning Component
We have done it! We have finished our component.
But because that component is “dumb”, it does not do anything other than displaying items and passing through user interaction.
To make it work, we need to handle all the events outside of the component.
At this point, you can either read further, and implement an in-memory solution with me, or go on your own and develop against your own API for example.
Did you decide to stay? Great!
In the rest of the tutorial, we will discover how to use that component, we have built.
Make sure to import the FileExplorerModule into the AppModule to get started!
The File Service
That in-memory solution I’ve talked about will live inside of a separate service. Let’s create one and call it fileService.
ng generate service service/file
That service will implement an interface that looks like this:
Nothing special. Just a simple CRUD (create, read, update, delete) interface.
The core of the service is a map. This map will contain all of our FileElements.
The methods above just use the map.
Here is how the service looks like:
Using the FileService to Feed the FileExplorer
Next, we use that file-service, to get our FileElements and make changes to them using the file-explorer.
To do that, we implement all events of the explorer in our app-component. Also, we provide the required inputs.
The first three inputs are just fields of our component:
The addFolder, removeElement, moveElement and renameElement are basically just passing through the event-payload to the file-service.
They also call the updateFileElementQuery method. This method causes the service to query all the FileElements for the current folder and update the array. If no currentRoot is specified, the constant ‘root’ is used instead of the id of the folder. That means we are currently at the root of the folder-tree.
The navigateUp and navigateToFolder methods are responsible for navigation. They change the state by updating the currentRoot element. Afterward, they also trigger an update of the fileElements array.
They also call the methods pushToPath and popFormPath. These methods are responsible for calculating the current file-path as a string. They are used to update the currentPath variable, which is passed to the file-explorer to be displayed in the header of the component.
Conclucion
Puhh, this tutorial was a lot of coding.
I hope you liked it anyway!
If you did, please share it with your friends and colleges!
Of course you can look up the full source code at the corresponding GitHub repository.