Gydunhn's Blog

Optimizing Folder Structure in Angular Projects

Angular

Introduction

The organization of files and folders may seem like a secondary aspect when developing Angular applications, but it's actually an architectural decision that has a profound impact on maintainability, scalability, and understanding of the project. In this article, we'll explore different approaches to structuring Angular projects, from the simplest to the most business domain-oriented.

When we start a project with Angular CLI, we're provided with a default structure. However, as the application grows, this initial structure may prove insufficient. Should we follow the framework conventions or adapt the structure to better reflect our business domain?

The Difference Between Scaffolding and Folder Structure

Before diving deeper, it's important to distinguish between two related but different concepts:

Scaffolding refers to the automatic process of generating code and initial structure using tools like Angular CLI. Commands like ng new or ng generate component automatically create files with boilerplate code and register them in the corresponding modules.

Folder structure is the final organization of files and directories in the project, which can be the result of the initial scaffolding, but also of conscious decisions about how to organize the code according to the specific needs of the project.

Scaffolding is a process that occurs at specific moments during development, while folder structure evolves continuously and is more under the control of the development team.

Three Levels of Structure in Angular Projects

We can identify three main approaches to organizing our Angular projects, each with different levels of complexity and benefits:

Level 1: Structure by File Type

This is the most basic structure, generally created by default with Angular CLI:

└── src/
    β”œβ”€β”€ app/
    β”‚   β”œβ”€β”€ components/
    β”‚   β”œβ”€β”€ services/
    β”‚   β”œβ”€β”€ models/
    β”‚   β”œβ”€β”€ pipes/
    β”‚   β”œβ”€β”€ directives/
    β”‚   └── guards/
    β”œβ”€β”€ assets/
    β”œβ”€β”€ environments/
    └── ...

Advantages:

Disadvantages:

Ideal use cases:

This structure is perfect when we need to quickly validate an idea or concept, without worrying too much about long-term scalability.

Level 2: Structure by Features

This approach groups code first by application features:

└── src/
    β”œβ”€β”€ app/
    β”‚   β”œβ”€β”€ core/                 # Singleton services, global guards, etc.
    β”‚   β”‚   β”œβ”€β”€ services/
    β”‚   β”‚   β”œβ”€β”€ guards/
    β”‚   β”‚   └── interceptors/
    β”‚   β”œβ”€β”€ shared/               # Shared components, pipes, directives
    β”‚   β”‚   β”œβ”€β”€ components/
    β”‚   β”‚   β”œβ”€β”€ directives/
    β”‚   β”‚   └── pipes/
    β”‚   β”œβ”€β”€ features/             # Application features
    β”‚   β”‚   β”œβ”€β”€ auth/
    β”‚   β”‚   β”‚   β”œβ”€β”€ components/
    β”‚   β”‚   β”‚   β”œβ”€β”€ services/
    β”‚   β”‚   β”‚   └── auth.module.ts
    β”‚   β”‚   β”œβ”€β”€ payment/
    β”‚   β”‚   β”‚   β”œβ”€β”€ components/
    β”‚   β”‚   β”‚   β”œβ”€β”€ services/
    β”‚   β”‚   β”‚   └── payment.module.ts
    β”‚   β”‚   └── employees/
    β”‚   β”‚       β”œβ”€β”€ components/
    β”‚   β”‚       β”œβ”€β”€ services/
    β”‚   β”‚       └── employees.module.ts
    β”‚   └── app.module.ts
    β”œβ”€β”€ assets/
    β”œβ”€β”€ environments/
    └── ...

Advantages:

Disadvantages:

Ideal use cases:

This structure is probably the most widely used in medium-sized Angular projects, as it offers a good balance between organization and ease of understanding.

Level 3: Structure with Screaming Architecture

This approach is inspired by the concept of "Screaming Architecture" proposed by Robert C. Martin, where the project structure should "scream" its business purpose:

└── src/
    β”œβ”€β”€ app/
    β”‚   β”œβ”€β”€ core/                  # Application infrastructure
    β”‚   β”‚   β”œβ”€β”€ infrastructure/    # Technical implementations
    β”‚   β”‚   β”‚   β”œβ”€β”€ http/
    β”‚   β”‚   β”‚   β”œβ”€β”€ storage/
    β”‚   β”‚   β”‚   └── auth/
    β”‚   β”‚   └── ui/               # Base UI components
    β”‚   β”‚       β”œβ”€β”€ layout/
    β”‚   β”‚       └── components/
    β”‚   β”œβ”€β”€ domains/              # Business domains (modules)
    β”‚   β”‚   β”œβ”€β”€ hr-management/    # Domain: HR Management
    β”‚   β”‚   β”‚   β”œβ”€β”€ employee-directory/  # Use case
    β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ components/
    β”‚   β”‚   β”‚   β”‚   β”œβ”€β”€ services/
    β”‚   β”‚   β”‚   β”‚   └── models/
    β”‚   β”‚   β”‚   β”œβ”€β”€ payroll/      # Use case
    β”‚   β”‚   β”‚   └── hr-management.module.ts
    β”‚   β”‚   β”œβ”€β”€ sales/            # Domain: Sales
    β”‚   β”‚   β”‚   β”œβ”€β”€ product-catalog/  # Use case
    β”‚   β”‚   β”‚   β”œβ”€β”€ checkout/     # Use case
    β”‚   β”‚   β”‚   └── sales.module.ts
    β”‚   β”‚   └── customer-support/ # Domain: Customer Support
    β”‚   β”‚       β”œβ”€β”€ ticket-management/  # Use case
    β”‚   β”‚       β”œβ”€β”€ knowledge-base/     # Use case
    β”‚   β”‚       └── customer-support.module.ts
    β”‚   β”œβ”€β”€ shared/               # Components shared between domains
    β”‚   └── app.module.ts
    β”œβ”€β”€ assets/
    β”œβ”€β”€ environments/
    └── ...

Advantages:

Disadvantages:

Ideal use cases:

What is Screaming Architecture?

The concept of "Screaming Architecture" was proposed by Robert C. Martin (Uncle Bob) in 2011. The central idea is that a project's architecture should clearly communicate its purpose and business domain, not the technology or framework used.

The analogy he offers is clear: when looking at an architect's plans, you immediately identify whether it's a house, an airport, or a shopping mall, without needing to see what materials will be used. Similarly, when opening a software project, we should be able to immediately identify what it does (a podcast application, a ticket sales system, etc.), not just what technology it uses (Angular, React, etc.).

In the context of Angular, this means:

Practical Implementation in Angular

Let's see how we can implement these structures in Angular, focusing especially on the Screaming Architecture approach:

1. Using Modules for Domains

Angular has a module system that perfectly fits the concept of domains:

// domains/hr-management/hr-management.module.ts
@NgModule({
  declarations: [...],
  imports: [
    CommonModule,
    SharedModule,
    RouterModule.forChild([
      { path: 'employees', component: EmployeeListComponent },
      { path: 'payroll', component: PayrollComponent }
    ])
  ],
  providers: [EmployeeService, PayrollService]
})
export class HrManagementModule { }

2. State by Domain (with NGRX/NGXS)

If you use state management, each domain can have its own state:

└── domains/
    └── hr-management/
        β”œβ”€β”€ store/
        β”‚   β”œβ”€β”€ actions/
        β”‚   β”œβ”€β”€ reducers/
        β”‚   β”œβ”€β”€ effects/
        β”‚   └── selectors/
        └── ...

3. Barrel Pattern

Facilitates imports using the barrel pattern:

// domains/hr-management/index.ts
export * from './models';
export * from './services';
export * from './components';

4. Lazy Loading

Take advantage of Angular's lazy loading to load domains only when needed:

// In app-routing.module.ts
const routes: Routes = [
  {
    path: 'hr',
    loadChildren: () => import('./domains/hr-management/hr-management.module')
      .then(m => m.HrManagementModule)
  },
  {
    path: 'sales',
    loadChildren: () => import('./domains/sales/sales.module')
      .then(m => m.SalesModule)
  }
];

Transformation Example

To better illustrate the concept, let's see how we could transform a traditional structure into one based on Screaming Architecture:

Before (Type-based approach)

app/
β”œβ”€β”€ components/
β”‚   β”œβ”€β”€ product-list.component.ts
β”‚   β”œβ”€β”€ cart.component.ts
β”‚   └── checkout-form.component.ts
β”œβ”€β”€ services/
β”‚   β”œβ”€β”€ product.service.ts
β”‚   └── cart.service.ts
└── models/
    β”œβ”€β”€ product.model.ts
    └── cart-item.model.ts

After (Screaming Architecture approach)

app/
└── domains/
    β”œβ”€β”€ catalog/
    β”‚   β”œβ”€β”€ components/
    β”‚   β”‚   └── product-list.component.ts
    β”‚   β”œβ”€β”€ services/
    β”‚   β”‚   └── product.service.ts
    β”‚   └── models/
    β”‚       └── product.model.ts
    └── shopping/
        β”œβ”€β”€ components/
        β”‚   β”œβ”€β”€ cart.component.ts
        β”‚   └── checkout-form.component.ts
        β”œβ”€β”€ services/
        β”‚   └── cart.service.ts
        └── models/
            └── cart-item.model.ts

Practical Considerations

When to use each approach?

There is no universal "correct" structure. The choice depends on several factors:

For small projects (1-3 developers):

For medium projects (4-10 developers):

For large projects (10+ developers):

Gradual Migration

It's not necessary to do a complete refactoring immediately. You can adopt a gradual approach:

  1. Identify the main domains of your application
  2. Start reorganizing one domain at a time
  3. Use the barrel pattern to minimize changes in imports
  4. Implement lazy loading to reduce performance impact during migration

Here's a more humanized version of that summary section:

To Summarize

At the end of the day, what makes a folder structure truly "good" isn't about following trendy patterns or rigid rulesβ€”it's about what works for your team in the real world. The best structure is one that makes your developers smile when they navigate the codebase, not curse under their breath.

Think of your folder structure as a living, breathing part of your application. It should grow and evolve alongside your project, adapting to new challenges and insights your team gains along the way. Some of the most effective structures I've seen came from teams who weren't afraid to say, "This isn't working for us anymore," and make thoughtful changes.

Most recent posts

#Angular #Architecture #Development #Structure #Web