A scalable angular project structure is something I struggled to implement when I started working on my first real-world Angular project. I generated my fist app in the command-line and kept adding components, services and pipes on the app module.

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

Having worked on several Angular projects, I’ve come up with a structure that helped me build scalable and maintainable applications. I aim to share this project structure to help developers who are in the same position.

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 different to accommodate certain 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 colleague 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 module |-- guards |-- models |-- services |-- shared module |-- components |-- pipes |-- directives |-- validators |-- layout |-- customers (feature module) |-- customer-list |-- component files |-- customer.routing |-- users (feature module) |-- user-list |-- component files |-- user.routing |-- root module |-- module files |-- assets |-- favicon |-- fonts |-- images |-- environments |-- dev |-- prod
Code language: Markdown (markdown)

Root module

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

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

Core module

The CoreModule 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

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

Application-wide singleton services do not belong to the SharedModule, they should be in the CoreModule. SharedModule 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

The last type of module in this structure is a feature module. Feature modules are used to organise a distinct feature of an 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, assigning a feature module to a fellow developer is a great advantage to support development in parallel.

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

AppModule enables lazy-loaded routes to feature modules.

This allows each feature module has its own routing configuration, which keeps routes in corresponding feature modules. This approach makes it easy to identify and isolate the feature content.

// 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 item 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

To summarise, we explored a very opinionated and an easy way to structure an Angular application following the official style guide. I hope this guide helps you build better applications. Download the project source code below and drop a comment to let me know what you think!

Umut Esen

Umut is a certified Microsoft certified developer and has an MSc in Computer Science. He is currently working as a senior software developer in Edinburgh, UK. He is the primary author and the founder of onthecode.

This Post Has 5 Comments

  1. Danilo

    Hi there! Such a wonderful article, thanks!

  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. 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.

Leave a Reply