In this post, we create a scalable Angular project structure that is ideal for medium to large applications.

[toc]

Introduction

Although Angular CLI does a great job at generating a new project, it does not force you to use a specific folder structure. Your initial project contains a single module, which contains all of the components.

While the following structure fits my needs, it is by no means the best structure. It is a guideline and your project structure may need be tweaked to accommodate your requirements.

Project structure

The project closely follows the folders-by-feature structure from the Angular style guide. I love the fact that the style guide is highly opinionated as this sets standards for developers to follow.

In this structure, the application has four fundamental types of modules. Together, they form what I call the scalable project structure.

  • Root module
  • Core module
  • Shared module
  • Feature modules

Let’s take a look at the responsibility of each module and see how they help to create a better project strtucture.

|-- app
      |-- core
           |-- guards
               |-- auth.guard.ts
           |-- models
               |-- customer.ts
               |-- user.ts
           |-- services
               |-- service-x.ts
               |-- service-y.ts
           |-- enums
               |-- customer-type.enum.ts
           |-- utils
               |-- common.functions.ts
           |-- interceptors
               |-- token.interceptor.ts
               |-- spinner.interceptor.ts
      |-- shared
           |-- components
           |-- pipes
           |-- directives
           |-- validators
           |-- layout
      |-- features
          |-- customers
               |-- customer-page
                    |-- customer-page.component.html|css|ts
               |-- customer-detail
                    |-- customer-detail.component.html|css|ts
               |-- customer-list
                    |-- customer-list.component.html|css|ts
               |-- customers-routing.module.ts
               |-- customers.module.ts
          |-- users
               |-- user-page
                    |-- user-page.component.html|css|ts
               |-- user-detail
                    |-- user-detail.component.html|css|ts
               |-- user-list
                    |-- user-list.component.html|css|ts
               |-- users.routing.module.ts
               |-- users.module.ts
      |-- app.component.ts
      |-- app-routing.module.ts
      |-- app.module.ts
|-- assets 
      |-- favicon
           |-- android-icon-*.png
           |-- apple-icon-*.png
           |-- ms-icon-*.png
           |-- favicon.ico
      |-- fonts
           |-- custom-font.woff
      |-- images
           |-- logo.png
           |-- user.png
|-- environments
      |-- environment.prod.ts
      |-- environment.ts
|-- index.html
|-- styles.cssCode language: Markdown (markdown)

Root module

AppModule is the entry point of the application and has the following responsibilities:

  • Bootstrap the application with AppComponent.
  • Import global dependencies such as BrowserModule, CoreModule.
  • Setup routing for the whole application, enabling lazy-loading for feature modules.

Core module

The core module of the application is responsible for keeping global services.

Most likely, these services will be HTTP services to communicate with a back-end API. I also use the core module to store guards, models and other global dependencies such as http interceptor and global error handler.

Please note that there should only ever be a single core module. This is so that the services registered in the core module are only instantiated once in the lifetime of the app. You can conveniently force the single-use of the core module.

Shared module

Shared module contains code that will be used across your feature modules. You only import the shared module into the specific feature modules. Ensure you don’t import it into your AppModule or CoreModule.

Application-wide singleton services do not belong to the shared module, they should be in the CoreModule. The shared module is only for keeping common components, pipes & directives. Layout component is a great example of a shared component.

@NgModule({
  imports: [
    RouterModule,
    FormsModule,
    ReactiveFormsModule,
    FlexLayoutModule,
  ],
  declarations: [
    LayoutComponent
  ],
  exports: [
    FormsModule,
    ReactiveFormsModule,
    FlexLayoutModule
  ],
  entryComponents: [ ]
})
export class SharedModule { }Code language: TypeScript (typescript)

Feature modules

Feature modules are used to organise a distinct feature of an application. Generally, each feature module represents a ‘feature slice’ in your application.

For example, you can create a feature module to encapsulate all functionality regarding customer management or account management.

Similarly, you can create a feature module to keep all imports of Angular Material or another third party control library such as Kendo UI.

Feature modules not only make your application structure better organised, they also allow isolated testing.

In addition, feature modules make it easier for multiple streams of work in the same project. Several features of an application can be developed in parallel with minimal conflicts.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CustomersRoutingModule } from './customers-routing.module';
import { SharedModule } from '../shared/shared.module';
import { CustomerListComponent } from './customer-list/customer-list.component';
@NgModule({
  imports: [
    CommonModule,
    CustomersRoutingModule,
    SharedModule
  ],
  declarations: [
    CustomerListComponent
  ],
  entryComponents: []
})
export class CustomersModule { }Code language: TypeScript (typescript)

Routing

In this project structure, AppModule defines routes that lazy-load feature modules.

This allows each feature module has its own routing configuration, which keeps routes in corresponding feature modules.

// app-routing.module.ts
const appRoutes: Routes = [
    {
        path: '',
        loadChildren: './customers/customers.module#CustomersModule'
    },
    {
        path: 'customers',
        loadChildren: './customers/customers.module#CustomersModule'
    },
    {
        path: 'users',
        loadChildren: './users/users.module#UsersModule'
    },
    {
        path: '**',
        redirectTo: '',
        pathMatch: 'full'
    }
];Code language: TypeScript (typescript)

First object in the routes array loads the customers module.

We then lazy-load other feature modules using a custom URL pattern and the path to the module.

Finally, we ensure that any path that does not match the routing configuration is redirected to the default module with ** pattern.

As an example, you can setup child routes in a feature module like below.

// customers-routing.module.ts
const routes: Routes = [
  {
    path: '',
    component: LayoutComponent,
    children: [
      { path: '', component: CustomerListComponent },
    ]
  }
];Code language: TypeScript (typescript)

Summary

In this post, we explored a very opinionated Angular project structure following the official style guide. I hope this guide helps you build better applications.

Check out my Angular starter project template based on this project structure.

Umut Esen

Software Engineer specialising in full-stack web application development.

Leave a Reply

This Post Has 5 Comments

  1. Gerry

    Hi there!
    Nice article!
    Why ensure to don’t import the SharedModule into your AppModule? Whats the reason to dont do that?

    1. Umut Esen

      That is because the shared module includes components, directives, and pipes which are re-used and referenced by the components declared in other feature modules. So if you import the shared module in the app module, you would be importing it twice in your application.

      1. kalempir

        Actually if you import the shared module in the app module, you don’t need reimport it in feature modules. In your case, when we import the shared module in feature modules, it will multiple times imported.

  2. canadianorderpharmacy

    Having read this I believed it was extremely enlightening. I appreciate you finding the time and effort to put this content together. I once again find myself spending way too much time both reading and commenting. But so what, it was still worthwhile!

  3. Danilo

    Hi there! Such a wonderful article, thanks!