Skip to main content

Angular Folder Structure

Introduction

A well-organized folder structure is crucial for maintaining Angular applications, especially as they grow in size and complexity. Just like a well-organized toolbox makes it easier to find what you need, a proper folder structure helps developers quickly locate files, understand the application architecture, and collaborate effectively with team members.

In this guide, we'll explore the recommended Angular folder structure, understand the purpose of each directory, and learn how to organize our code for better maintainability and scalability.

Default Angular Folder Structure

When you create a new Angular application using the Angular CLI (ng new my-app), it generates a standard folder structure. Let's first understand this structure before discussing how to customize it for larger applications.

my-app/
├── node_modules/
├── src/
│ ├── app/
│ │ ├── app-routing.module.ts
│ │ ├── app.component.css
│ │ ├── app.component.html
│ │ ├── app.component.spec.ts
│ │ ├── app.component.ts
│ │ └── app.module.ts
│ ├── assets/
│ ├── environments/
│ ├── favicon.ico
│ ├── index.html
│ ├── main.ts
│ ├── polyfills.ts
│ ├── styles.css
│ └── test.ts
├── angular.json
├── package.json
├── tsconfig.json
└── ... (other config files)

Understanding the Key Directories

src/
This contains the source code of your application.

src/app/
The main application code resides here. Initially, it contains the root component and module.

src/assets/
Static files like images, icons, and fonts go here.

src/environments/
Configuration files for different environments (development, production, etc.).

src/main.ts
The main entry point that bootstraps the Angular application.

Organizing Larger Applications

As your application grows, the default structure might become unwieldy. Here's a recommended structure for organizing larger Angular applications:

src/
├── app/
│ ├── core/ ← Core functionality used throughout the app
│ │ ├── guards/
│ │ ├── interceptors/
│ │ ├── services/
│ │ └── core.module.ts
│ ├── features/ ← Feature modules
│ │ ├── home/
│ │ ├── dashboard/
│ │ └── user-profile/
│ ├── shared/ ← Shared components, directives, pipes
│ │ ├── components/
│ │ ├── directives/
│ │ ├── pipes/
│ │ └── shared.module.ts
│ ├── app-routing.module.ts
│ ├── app.component.ts
│ └── app.module.ts
└── ... (other files)

Let's go deeper into each section:

Core Module

The Core module contains singleton services, universal components, and other features where there's only one instance per application, such as a navigation bar or authentication service.

typescript
// src/app/core/core.module.ts
import { NgModule, Optional, SkipSelf } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { NavbarComponent } from './components/navbar/navbar.component';
import { AuthService } from './services/auth.service';

@NgModule({
declarations: [NavbarComponent],
imports: [CommonModule, HttpClientModule],
exports: [NavbarComponent],
providers: [AuthService]
})
export class CoreModule {
constructor(@Optional() @SkipSelf() parentModule: CoreModule) {
if (parentModule) {
throw new Error('CoreModule is already loaded. Import it only in AppModule');
}
}
}

The constructor prevents the CoreModule from being imported more than once.

Shared Module

The Shared module contains components, directives, and pipes that are used in multiple feature modules.

typescript
// src/app/shared/shared.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ButtonComponent } from './components/button/button.component';
import { HighlightDirective } from './directives/highlight.directive';
import { TruncatePipe } from './pipes/truncate.pipe';

@NgModule({
declarations: [ButtonComponent, HighlightDirective, TruncatePipe],
imports: [CommonModule, FormsModule, ReactiveFormsModule],
exports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
ButtonComponent,
HighlightDirective,
TruncatePipe
]
})
export class SharedModule { }

Feature Modules

Feature modules organize code for specific application features. Each feature module should follow a similar structure:

features/
└── dashboard/
├── components/ ← Components specific to this feature
│ ├── dashboard.component.ts
│ └── widget.component.ts
├── models/ ← Data models/interfaces
│ └── widget.model.ts
├── services/ ← Services used only in this feature
│ └── dashboard.service.ts
└── dashboard.module.ts ← The feature module

Here's an example of a feature module:

typescript
// src/app/features/dashboard/dashboard.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { SharedModule } from '../../shared/shared.module';
import { DashboardComponent } from './components/dashboard.component';
import { WidgetComponent } from './components/widget.component';
import { DashboardService } from './services/dashboard.service';

const routes: Routes = [
{ path: '', component: DashboardComponent }
];

@NgModule({
declarations: [DashboardComponent, WidgetComponent],
imports: [SharedModule, RouterModule.forChild(routes)],
providers: [DashboardService]
})
export class DashboardModule { }

App Module

The App Module ties everything together:

typescript
// src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { CoreModule } from './core/core.module';

@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, AppRoutingModule, CoreModule],
bootstrap: [AppComponent]
})
export class AppModule { }

Lazy Loading Features

For better performance, implement lazy loading for feature modules in your routing:

typescript
// src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

const routes: Routes = [
{
path: 'dashboard',
loadChildren: () => import('./features/dashboard/dashboard.module')
.then(m => m.DashboardModule)
},
{
path: 'user-profile',
loadChildren: () => import('./features/user-profile/user-profile.module')
.then(m => m.UserProfileModule)
},
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
];

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

Real-World Example: E-commerce Application

Let's structure an Angular e-commerce application:

src/
├── app/
│ ├── core/
│ │ ├── guards/
│ │ │ └── auth.guard.ts
│ │ ├── interceptors/
│ │ │ └── token.interceptor.ts
│ │ ├── services/
│ │ │ ├── auth.service.ts
│ │ │ └── cart.service.ts
│ │ ├── components/
│ │ │ ├── header/
│ │ │ └── footer/
│ │ └── core.module.ts
│ ├── features/
│ │ ├── home/
│ │ ├── product/
│ │ │ ├── components/
│ │ │ │ ├── product-list/
│ │ │ │ └── product-detail/
│ │ │ ├── models/
│ │ │ │ └── product.model.ts
│ │ │ ├── services/
│ │ │ │ └── product.service.ts
│ │ │ └── product.module.ts
│ │ ├── cart/
│ │ └── checkout/
│ ├── shared/
│ │ ├── components/
│ │ │ ├── star-rating/
│ │ │ └── product-card/
│ │ ├── directives/
│ │ │ └── image-fallback.directive.ts
│ │ ├── pipes/
│ │ │ └── currency-format.pipe.ts
│ │ └── shared.module.ts
│ ├── app-routing.module.ts
│ ├── app.component.ts
│ └── app.module.ts
└── ... (other files)

Let's examine how a product service might be implemented in this structure:

typescript
// src/app/features/product/models/product.model.ts
export interface Product {
id: string;
name: string;
price: number;
description: string;
imageUrl: string;
category: string;
}

// src/app/features/product/services/product.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Product } from '../models/product.model';
import { environment } from '../../../../environments/environment';

@Injectable()
export class ProductService {
private apiUrl = `${environment.apiUrl}/products`;

constructor(private http: HttpClient) { }

getProducts(): Observable<Product[]> {
return this.http.get<Product[]>(this.apiUrl);
}

getProduct(id: string): Observable<Product> {
return this.http.get<Product>(`${this.apiUrl}/${id}`);
}
}

And a component using this service:

typescript
// src/app/features/product/components/product-list/product-list.component.ts
import { Component, OnInit } from '@angular/core';
import { ProductService } from '../../services/product.service';
import { Product } from '../../models/product.model';

@Component({
selector: 'app-product-list',
template: `
<div class="product-grid">
<app-product-card
*ngFor="let product of products"
[product]="product">
</app-product-card>
</div>
`
})
export class ProductListComponent implements OnInit {
products: Product[] = [];

constructor(private productService: ProductService) { }

ngOnInit(): void {
this.productService.getProducts()
.subscribe(products => this.products = products);
}
}

Best Practices for Angular Folder Structure

  1. Follow the Separation of Concerns principle: Keep your code organized based on what it does rather than its type.

  2. Each feature should be its own module: This enables lazy loading and better separation of concerns.

  3. Use barrels for clean imports: Create index.ts files to export components from a folder.

    typescript
    // src/app/shared/components/index.ts
    export * from './button/button.component';
    export * from './card/card.component';

    // Then you can import like this
    import { ButtonComponent, CardComponent } from '@app/shared/components';
  4. Create aliases for common paths: In tsconfig.json:

    json
    {
    "compilerOptions": {
    "paths": {
    "@app/*": ["src/app/*"],
    "@core/*": ["src/app/core/*"],
    "@shared/*": ["src/app/shared/*"],
    "@env/*": ["src/environments/*"]
    }
    }
    }
  5. Keep components small and focused: If a component file exceeds 300 lines, consider breaking it down.

  6. Group by feature, not by type: Avoid organizing by types (e.g., all services in one folder).

Summary

A well-structured Angular application improves:

  • Maintainability: Developers can easily find and update code
  • Scalability: The application can grow without becoming chaotic
  • Team collaboration: Clear organization helps teams work effectively
  • Performance: Through lazy loading and proper module separation

The recommended structure uses:

  • A Core module for application-wide singleton services
  • Feature modules for specific functionality
  • A Shared module for common components
  • Proper lazy loading for better performance

By following these guidelines, you'll create Angular applications that are easier to develop, maintain, and extend over time.

Additional Resources

Exercises

  1. Create a basic Angular application and restructure it according to the guidelines in this article.
  2. Identify a large component in your existing Angular application and break it down into smaller, more focused components.
  3. Implement lazy loading for at least one feature module in your application.
  4. Add path aliases in your tsconfig.json and update your imports to use them.
  5. Create a shared component that's used across multiple feature modules in your application.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)