The folder structure or the way in which your files and folders are organized is the backbone of your project. It is one of the most underrated and ignored aspects of development, yet the most important.
How to spot a good folder structure?
As your application grows in size, so does the codebase. If you or another developer who isn’t aware of the codebase is able to locate the file he/she wants to edit fast – only then you have a good folder structure. The faster you are able to locate the file you want to work on, the better the folder structure.
If you are comparing projects of a very small size, then you won’t be able to see the difference but as and when the project size increases, you’ll start feeling a resistance (if your folder structure sucks) in development.
Identifying if your folder structure sucks
If you are working in a team and are working on a big project, you all need to sit and write down some ground rules so that you all follow the same folder structure because if you don’t, merging code will be a nightmare.
The following are the points that I have personally experienced while working with my team over the course of 1 year because of a fucked up folder structure:
- If you are getting confused time and again due to conflicting file names
- You are unable to locate the file you want to edit within 2-3 seconds.
- The files and folders are not following the same naming conventions
- The files are so organized that it goes against the best practices of the framework (Angular) or library (ReactJS) you are working on
- Adding a new folder or file seems like a life decision as to where to add, how to add and what to name.
- Every folder or file which is getting added to code-base is making you anxious
The folder structure I follow
Please find the demo application on my Github – Click here
I advise you to clone the repo and then read the blog so that you can relate to it. After that, try to implement it all by yourself.
Disclaimer: This is not THE FOLDER STRUCTURE. This is also not a folder structure that is suitable for every single Angular application out there. There are plenty of others that are given by developers far more experienced and credible than I am. This is something that I follow and have been following to build scalable Angular applications for over a year without hitting a wall.
Before diving into my own folder structure, I would like you to read (in exact order) these two articles which have helped me immensely in shaping and understanding my own folder structure.
- Angular Folder Structure by Tom Cowley
- How to define a highly scalable folder structure for your Angular project by Mathis Garberg
I’ll try to keep it as practical as possible with explanations so that all you have to do is FOLLOW along.
STEP 1: Generate a new Angular app
Create a folder with the name `scalable-angular-app` and open it in VS CODE (You can use any IDE). Generate a new angular application in the current directory with the flag for SCSS so that all the stylesheets are SASS based.
ng new scalable-angular-app --directory ./ --style=scss
STEP 2: Add the CORE module
The core module is one of the most important modules which gets ignored or overlooked very easily. The core module contains everything which the angular app requires to start. Anything which the entire application uses goes into the core module. The following things go into the core module:
Components – It will contain the components which are common throughout the application like Header, Navbar, and Footer.
Guards – These are special services that help to grant or revoke access to certain parts of the application.
Interceptors – They help to intercept or modify outgoing requests or incoming responses. Mainly two types of interceptors are being used – the HttpTokenInterceptor and the HttpErrorInterceptor.
Layouts – A single angular application can consist of various different views that are common for different parts of the application. We call them layouts. Not only they are very handy to add universal loaders, toast messages, the confirmation dialog boxes but also helps to get initial data from backend to populate the app i.e. it works as an entry component.
Models – The is a debatable topic as to where one puts models. I personally believe that they should be kept in the core module as the entire app will be using them.
Routes – The folder will contain one file named all-routes.ts which will contain all the routes in lazy-loaded fashion.
Services – One of the biggest mistakes that developers out there commit is that they fail to understand how Angular’s dependency injection works. Just because multiple feature modules use the same service, they add the service to the “shared module” ‘s providers and then import the shared module into their feature modules. Now, this fucks the entire system up because now every feature module is creating a local copy of the service and is using its local copy. In other words, if 5 feature modules are using the “auth service” and each one of them has been importing the shared module, there are 5 copies of the same auth service in the app. The golden rule that you have to remember is that all the services have to be a singleton i.e. all the feature modules will use the same copy of the service.
Utilities – The folder consists of files and folders which lends a helping hand. I have two files here namely custom-validators.ts and utility.service.ts. The names are pretty self-explanatory. The former file has custom form validators which I use while developing reactive forms and the latter one contains code snippets (a bunch of functions) which help me in achieving minor tasks.
Generate the core module in `app` directory
ng g m core
Running this command will generate a folder named `core` with the file name core.module.ts.
Next, add the remaining folders stated above inside the core module.
We’ll start by adding project-layout to the “layouts” folder. This is a component that will hold the structure of the main app. We can say this takes the place of app.component.html/.ts
Switch to the “layouts” folder and then fire:
ng g c project-layout
Next, we’ll be adding the most commonly used 2 components – header and footer to the components folder. Switch to the components folder and fire:
ng g c header
ng g c footer
Two components that I use extensively are:
- not-found404: This component is loaded when a route is not found
- scroll-to-top: This adds a small floating button on the bottom right corner which will help scroll to top of the page after a certain amount of scroll.
The components will be automatically declared in the core module. You can then easily use them in the “projects-layout” component.
After this, we’ll be going towards the “guards” and “interceptors“.
The only guard we’ll have for now is “EnsureModuleLoadedOnceGuard“. You can have an Auth Guard or a Route Guard as well, but let’s save it for some other time. The following guard ensures that the module where this guard is present is loaded only once and if by mistake it gets loaded somewhere else, it will throw an error.
The code for the module is present in the file ensure-module-loaded-once.guard.ts and the guard is imported in the core module.
After this, its time for the interceptors. For now, we’ll be adding the 2 interceptors but not be using them. I’ll be writing a separate post (an extension of the current post) on using the guards and interceptors. The two interceptors are:
- http-error.interceptor.ts – Will help to handle the incoming responses
- http-token.interceptor.ts – Will help in modifying the outgoing requests with authentication tokens etc.
Routing and Services
I decided to put this into a separate section because of its importance. First going with the routing, there will be one file all-routes.ts in the folder “routes“. The file will contain the path to all the feature modules but with lazy loading. We’ll be ensuring that the feature module is loaded in a lazy fashion by using the “loadChildren” key while specifying the path.
Next, the services will be added to the “services” folder and all these services will be provided in the “providers” array of the core module. For now, I have added the base.service.ts where we are using the environment variables to get the base url. Don’t worry, I’ll be using all this in the upcoming posts wherein I’ll extend this to create the angular boilerplate.
The final core module looks like this:
STEP 3: Import the core module in app module
Add the core module in the `imports` array of app.module.ts
Make sure you not only import the modules you need in the core module, but also export the modules and components that you want the app.module.ts to access explicitly. For example, I am exporting `HttpClientModule` from the core module.
STEP 4: Add the Home module (A feature module)
The home module will be the module that loads when the app starts for the first time. This will, like others, will be a feature module that will use lazy loading to load.
Lazy loading (also called on-demand loading) is an optimization technique for the online content, be it a website or a web app.
Instead of loading the entire web page and rendering it to the user in one go as in bulk loading, the concept of lazy loading assists in loading only the required section and delays the remaining, until it is needed by the user.https://www.geeksforgeeks.org/what-is-lazy-loading/
Generate the home module in the “app” folder along with the routing file.
ng g m home --routing
The `–routing` flag will ensure that the routing file (home-routing.module.ts) for this module is generated.
The home module will be created besides the core and the app module.
No matter the size of the angular application, I always create the feature module in the same way. The structure I follow to create the feature modules not only helps me to keep the codebase homogenous, but it helps me make the feature modules future-proof for scalability.
Entry component of a feature module
Once the home module with its routing file is created, next I create the “ENTRY COMPONENT” for this feature module. This entry component has the name “home-layout”. Go to the home module directory and generate the entry component for this module:
ng g c home-layout
This component will take care of the layout of the home module. If the module doesn’t have some special layout, just give `<router-outlet></router-outlet>` to its .html file and let it be. But if you want your home module to have a different layout than other feature modules, this is the place to make it happen.
This is also the component wherein you can call the API’s for loading the initial data for the entire home module.
Pages in a feature module
You’ll need a bit of imagination or far-sightedness to understand this concept. This concept is majorly used in React.
Consider that our angular application has 3 static pages:
- Landing page
- About us page
- Contact us page
Now, these make 3 pages in the home module. Pages are independent entities in a given feature module.
Therefore, create a folder “pages” inside the feature module directory. Then, go to the pages directory and generate 3 pages (or components):
ng g c landing
ng g c about-us
ng g c contact-us
Shared components for a specific feature module
Do not get alarmed or act like a know-it-all. Just hear me out. So the thing is there are two kinds of shared components. One, which is shared across feature modules and the other which are shared between pages of the same feature module. The former components will go in the “shared-components” folder of “shared-module” but the latter components which are being shared between the pages of a single module will go in the “components” folder of the feature module.
Read it thrice and you’ll get it.
Promise. Pinky Promise!
Create a folder “components” in the home module directory.
Now, imagine that the “contact-us” and “about-us” both use the “testimonial” component, but no other feature module uses this “testimonial” component.
Hence, generate the “testimonial” component in the “components” folder and use it in both “contact-us” and “about-us” pages.
Move to the “components” folder and fire:
ng g c testimonial
This is how the “about-us” and “contact-us” pages are using the “testimonial” shared component.
The about-us component:
The contact-us component
Routing of a feature module
The routing is really simple here. You open the component with the entry-component “HomeLayoutComponent” and all the pages are then are child routes for this module. I have loaded the “LandingComponent” on the root path (the path on which this feature module is called).
STEP 5: Get app-routing.module.ts ready!
The project-layout defined in the core module can now be used to launch the app with all-routes as the children routes.
STEP 6: Add the Shared module
Lastly, we add the shared module. Here we store:
- Modules – being used by multiple feature modules
- Components – being used by multiple feature modules
We just export from the shared module whatever we want to import in the feature modules.
Generate the shared module in the “app” folder:
ng g m shared
Add the folders – directives, pipes, modules, and components
Step 7: Launch
Find the repo here: https://github.com/adityatyagi/scalable-angular-folder-structure
This is the folder structure that I believe is scalable as well as robust. I use it on a daily basis and have developed some of the most complex projects – all thanks to this folder structure.
Give it a try and do let me know how if you liked it.
Suggestions, feedback, and questions? Ask in the comments section below!