DEV Community

Victor Tihomirov
Victor Tihomirov

Posted on

A simple Angular folder structure that makes development feel natural and easy.

After years of working on multiple Angular projects, I have had firsthand experience with the difficulties of poorly thought out designs. Projects that were supposed to be scalable were unscalable, had unreadable code, and did not adhere to the Angular coding style guide. Luckily, the coding style guide already gives us a place to start when it comes to project structure, with a shared module and a module for each feature.

In this guide, I will go over the feature-based approach in greater detail and explain the whys and hows.

First of all, why is a well-designed folder structure important?

  • It allows us to be more flexible with our application. We can add new functionality without breaking anything.
  • A well-designed system is simple to understand and navigate.
  • It improves testing, maintainability, and reusability.

I like to split the project into six main folders, each with its own responsibility: core, features, shared, apis, types, and store.

Core folder

The CoreModule is located in the core folder and contains essential services, components, and other functionality that are required by many parts of the application, either once (components) or as a singleton (services). Here are some examples of such components and services:

  • Components (header, footer, navbar, error, etc.)
  • Logging service
  • Exception handling service
  • Configuration service
  • Localization service
  • Auth service
  • Http interceptors
  • Guards

By encapsulating this functionality into a single module, we gain a centralized location for managing dependencies and configuring the application at a high level.

The CoreModule is typically imported once in the application's root module. Its components and services are made available to other modules via dependency injection.

To avoid accidentally importing the CoreModule more than once, include the following code in the constructor:

constructor(@Optional() @SkipSelf() parentModule: CoreModule) {
    if (parentModule) {
      throw new Error(`CoreModule has already been loaded. Import it in the AppModule only.`);
  }
}
Enter fullscreen mode Exit fullscreen mode

This is how the core folder might look.:

|-- core
|   |-- components
|   |   |-- header
|   |   |   |-- header.component.ts
|   |   |   |-- header.component.html
|   |   |   |-- header.component.scss
|   |   |-- footer
|   |   |   |-- footer.component.ts
|   |   |   |-- footer.component.html
|   |   |   |-- footer.component.scss
|   |-- services
|   |   |-- auth.service.ts
|   |   |-- logging.service.ts
|   |   |-- exception.service.ts
|   |-- interceptors
|   |   |-- auth.interceptor.ts
|   |-- guards
|   |   |-- auth.guard.ts
|   |-- core.module.ts
Enter fullscreen mode Exit fullscreen mode

Shared folder

The shared folder contains the SharedModule which typically contains reusable UI components used throughout the application, such as buttons, form fields, and input validation. It may also include directives and pipes that are used by numerous modules.

Developers can avoid code duplication across multiple modules by encapsulating these shared components in the 'SharedModule,' which would otherwise result in code bloat and maintenance issues. It also helps to enforce UI consistency because all modules will be using the same shared components.

Bonus: Create a ThirdPartyModule to differentiate between own components and vendor components such as NgbModule. This module should be imported into the SharedModule.

The shared folder might look like this:

|-- shared
|   |-- components
|   |   |-- spinner
|   |   |   |-- spinner.component.ts
|   |   |   |-- spinner.component.html
|   |   |   |-- spinner.component.scss
|   |   |-- modal
|   |   |   |-- modal.component.ts
|   |   |   |-- modal.component.html
|   |   |   |-- modal.component.scss
|   |-- directives
|   |   |-- highlight.directive.ts
|   |-- pipes
|   |   |-- capitalize.pipe.ts
|   |-- shared.module.ts
|   |-- third-party.module.ts
Enter fullscreen mode Exit fullscreen mode

Features folder

This folder contains the features of the application. Each feature has its own module that contains a collection of components, services, directives, pipelines, and other code that encapsulates a specific aspect of the application's functionality.

I like to think of a feature as a slice of pie. You should be able to easily remove a slice of pie without making a mess. This means that our features should not be dependent on one another.

The features folder can look like this:

|-- features
|   |-- products
|   |   |-- components
|   |   |   |-- product-list
|   |   |   |   |-- product-list.component.ts
|   |   |   |   |-- product-list.component.html
|   |   |   |   |-- product-list.component.scss
|   |   |   |-- product-details
|   |   |   |   |-- product-details.component.ts
|   |   |   |   |-- product-details.component.html
|   |   |   |   |-- product-details.component.scss
|   |   |-- product-root.component.html
|   |   |-- product-root.component.ts
|   |   |-- product-routing.module.ts
|   |   |-- product.module.ts
Enter fullscreen mode Exit fullscreen mode

Apis folder

The apis folder is used to store files related to consuming RESTful APIs or other web services. These files may include service classes, models or interfaces, and helper functions related to working with the APIs.

|-- apis
|   |-- product.service.ts
Enter fullscreen mode Exit fullscreen mode

Types folder

The types folder is a location for storing type definitions that are used across the application.

|-- types
|   |-- user
|   |   |-- user.ts
|   |   |-- user-status.enum.ts
|   |-- product
|   |   |-- product.ts
|   |   |-- product-type.enum.ts
Enter fullscreen mode Exit fullscreen mode

Store folder

If you're using NgRx for state management, the store folder is ideal for providing a centralized location for managing application state, which can help to simplify the codebase and make complex state interactions easier to manage.

|-- store
|   |-- user
|   |   |-- actions
|   |   |   |-- user.actions.ts
|   |   |-- reducers
|   |   |   |-- user.reducers.ts
|   |   |-- selectors
|   |   |   |-- user.selectors.ts
|   |-- product
|   |   |-- actions
|   |   |   |-- product.actions.ts
|   |   |-- reducers
|   |   |   |-- product.reducers.ts
|   |   |-- selectors
|   |   |   |-- product.selectors.ts
|   |-- store.module.ts
Enter fullscreen mode Exit fullscreen mode

In conclusion, the Angular world and the best practices are evolving, and so do we. I will come back again in a year and revise what can be improved (hello, signals!).

Keep learning, stay curious, and never stop coding!

Top comments (2)

Collapse
 
spock123 profile image
Lars Rye Jeppesen

Great article, thank you

Collapse
 
vixero profile image
Victor Tihomirov

I'm glad you enjoyed it.