Optimizing Folder Structure in Angular Projects
Gydunhn

Gydunhn @gydunhn

About: 🎸 I Wanna Rock n' Roll 🤘 All nite, and Party 🥳 Every Day! 🎶

Joined:
Mar 31, 2025

Optimizing Folder Structure in Angular Projects

Publish Date: Mar 31
0 0

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/
    └── ...
Enter fullscreen mode Exit fullscreen mode

Advantages:

  • Familiar to Angular developers
  • Follows framework conventions
  • Quick to implement

Disadvantages:

  • Does not communicate the business purpose
  • Becomes difficult to maintain as it grows
  • Related modules are scattered

Ideal use cases:

  • Prototypes (POC - Proof of Concept)
  • Minimum Viable Products (MVP)
  • Short-term projects
  • Very small teams (1-2 developers)

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/
    └── ...
Enter fullscreen mode Exit fullscreen mode

Advantages:

  • Organizes code by application features
  • Uses Angular modules to encapsulate functionalities
  • Facilitates parallel work between teams
  • Keeps shared components separate

Disadvantages:

  • Still maintains some technical orientation instead of domain focus
  • It can be difficult to identify boundaries between features

Ideal use cases:

  • Medium-sized projects (3-10 developers)
  • Applications with expectation of moderate growth
  • Teams with staff rotation

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/
    └── ...
Enter fullscreen mode Exit fullscreen mode

Advantages:

  • The structure clearly communicates the business purpose
  • Organizes code by domains and use cases
  • Facilitates business understanding for new developers
  • Each domain can evolve independently
  • Promotes separation of concerns

Disadvantages:

  • Greater initial complexity
  • Requires good knowledge of the business domain
  • May be excessive for small applications

Ideal use cases:

  • Large and complex projects
  • Numerous teams (10+ developers)
  • Long-life applications
  • Systems with well-defined business domains

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:

  • The framework is just a tool, not the foundation of our architecture
  • The structure should reflect domain concepts, not framework conventions
  • Critical decisions should be based on business needs, not framework features

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 { }
Enter fullscreen mode Exit fullscreen mode

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/
        └── ...
Enter fullscreen mode Exit fullscreen mode

3. Barrel Pattern

Facilitates imports using the barrel pattern:

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

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)
  }
];
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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):

  • Level 1 is ideal for POCs and MVPs where the priority is to quickly validate concepts
  • Level 2 is suitable if moderate growth is expected

For medium projects (4-10 developers):

  • Level 2 is usually the most balanced
  • Consider Level 3 if the domain is complex and well-defined
  • Implement lazy loading for modules/features
  • Evaluate using NgRx/NGXS with state separated by domain

For large projects (10+ developers):

  • Level 3 with Screaming Architecture offers the best long-term benefits
  • Consider dividing into multiple applications or microfrontends
  • Use shared libraries for common code between domains

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

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.

In to the future

Comments 0 total

    Add comment