An important task in setting up a modern front-end project is to define a scalable, long-term and future-proof folders structure, and the naming guidelines for each different programming entity.
While some think of this as a simple and secondary aspect — it often hides more complexities than it seems. Even though most times there is no perfect solution — we can explore some of the industry best practices, and some of the things that in my experience made the most sense.
In this article we’ll go through:
The first thing I often do when setting up a new codebase is to think and define the programming entities that make up my stack. As Angular developers, we know some of them pretty well already:
As suggested by the framework’s documentation, every time we create each one of these entities we will suffix the filename with the name of the entity.
Therefore — if we create a pipe whose class is called HighlightPipe, we will name its file highlight.pipe.ts, and if we have a component called DropdownComponent we want to its files dropdown.component.ts, dropdown.component.html and dropdown.component.scss.
We cannot talk about an Angular project’s structure without first talking about Angular Modules.
As Angular apps are made of modules that can import other modules, they naturally become the root folders that make up an Angular project. Each module will contain all other Angular entities contained in their own folders.
Let’s say we’re building an e-commerce application, and we create a shopping cart feature module, this is what its structure could look like:
💡As you may notice, I tend to differentiate between containers (smart) and components (dumb) so I place them in different folders, but it’s not something I necessarily advocate
A Feature Module is not supposed to export anything except the top component, so anything we define within it will not be used elsewhere.
What if something needs to be reused elsewhere, though?
In this case, we create a shared module SharedModule that will host all shared entities that will be provided to every module of the project.
A SharedModule is usually made up of entities that are shared across different modules within a project — but aren’t normally needed outside of it. When we do encounter services or components that can be reused across different teams and projects, and that ideally don’t change very often, we may want to build an Angular Library.
💡For a detailed overview of all the different module types, you can check that out on Angular’s official website.
When you are using highly-reusable services or components, which can be classified as Service Modules and Widget Modules, you may want to build these modules as Angular Libraries, which can be either be created in their own repository or in a larger monorepo.
Thanks to the powerful CLI, we can easily generate Angular libraries that will be built in a folder called projects with this simple command:
ng generate library my-lib
For a complete description regarding Angular libraries, have a look at the official documentation on Angular.io.
Using libraries has a few advantages over local modules:
With also some cons:
Example: Let’s say BigCompany uses a messaging system all teams use — we may want to share our abstraction to avoid many libraries essentially doing the usual groundwork.
So we create a library called messaging, and we publish it to NPM as @big-company/messaging.
But what about monorepos? and microfrontends?
This would probably need a larger article, but we can’t talk about enterprise-grade projects without mentioning these other two ways:
💡 You may want to take a look at Nx Workspaces
Building an Angular project as a monorepo containing more projects and libraries is an appealing solution, but practically difficult to undertake for massive technology companies, where many teams and projects are separate and far away from each other.
So where should be libraries built?
If you are using Angular with Typescript — and I assume you are, you also have to take into account Typescript’s own powerful entities that we can leverage to make a structured, well-written codebase.
Here is a list of Typescript entities that you’ll be using the most in your project:
I like to group these entities in their own folder within a module, which I reluctantly call core, but this is very much up to you and your team to decide.
I recommend creating a matching Typescript file for each back-end entity. This includes enums, DTOs (for both requests and responses), and data classes.
Sometimes, for example, we are going to be developing against a microservice shared by several teams within a company. In similar cases, I think it makes sense to build an angular library that will be hosting the matching classes, interfaces, and enums rather than developing the module locally.
Whatever state management library you’re planning on using, one thing I’d recommend is to keep the business logic separated from domain modules. We can leverage the Service Modules pattern and import it in its relative feature module.
A State Management service module only needs to export two things:
What advantages does this pattern have?
I like to keep the state separate from feature modules, which is a practice particularly popular but that still keeps the Angular community fairly divided:
What programming entities does NGRX have?
Let’s look at a brief example in the image below using NGRX, which I will be explaining in detail in a separate article.
💡 If we create a test for each file we create, it's a good idea to place them in a separate folder