Building a robust, maintainable, and scalable front-end application isn’t easy; many things have to be done. Well structure is one of the most important parts. In this post, I will share my preferred Angular structure with the Nx workspace
Prerequisite
- Angular 17
- Nx workspace
What is Nx?
In recent years, Nx workspace has become the most popular tool for building and managing applications. Nx is an open-source, technology-agnostic build platform designed to efficiently manage codebase of any scale. Nx understands our project relationship and dependencies, executes tasks smarter and faster!
Group
First of all, I separate the app into three main groups:
- Core - singleton services or other parts that should import only one when the application is instantiated
- Shared - Components or other parts that are shared entire our application
- Feature - Smart components that map one-to-one with our business
Each part of the group has a prefix that corresponds to the group’s name. For instance, core-service, shared-button-ui, feature-product-list. As my style, I like a flat folder over a nested folder 🙂.( nested folder style we structure like this: core/services shared/button-ui )
App Structure
After identifying which group we should put into. We have the structure below:
├── apps
│ └── my-app
└── libs
├── core-api
├── core-guard
├── core-interceptor
├── core-layout
├── core-service
├── customer
│ ├── feature-detail
│ └── feature-list
├── shared-button-ui
├── shared-hover-directive
├── shared-percent-pipe
└── shared-upper-case-pipe
Let's explain from top to bottom
Core-service
Type: lib (folder)
Tag: type: core
- Services which we intend to keep as a singleton, such as user-service,theme-service, often @injectable({provideIn:’root’})
Tip
Prevent CoreModule from being imported twice:
import { NgModule, Optional, SkipSelf } from '@angular/core';
@NgModule({
// declarations, imports, exports, etc.
})
export class CoreModule {
// This is the constructor guard
constructor(@Optional() @SkipSelf() parentModule?: CoreModule) {
if (parentModule) {
throw new Error(
'CoreModule has already been loaded. Import it only in the AppModule.'
);
}
}
}
Core-guard
Type: lib (folder)
Tag: type: core
- Guards we apply across our application, such as role-guard, auth-guard
Core-interceptor
Type: lib (folder)
Tag: type: core
- Interceptors we apply in our application, such as response-interceptor, error-interceptor
Core-api
Tag: type: core
Type: lib (folder)
- Api service which interacts with our back-end system, such as product-api,order-api, often @injectable({provideIn:’root’})
Core-layout
Tag: type: core
Type: lib (folder)
- Including all layouts which is loaded at the initial page load and loading only one, such as app-header, app-layout, app-footer
Shared-ui-[componet-name]
Tag: type: ui
Type: lib (folder)
- Components that we intend to share across our application. Keep it dump as much as possible, such as shared-ui-button, shared-ui-dropdown.
Shared-directive-[directive-name]
Tag: type: directive
Type: lib (folder)
- Directives which we intend to share across our application, such as shared-directive-hover, shared-directive-double-click, keep it separate, avoid putting it together
Shared-pipe-[pipe-name]
Tag: type: pipe
Type: lib (folder)
- Pipes which we intend to share across our application, such as shared-pipe-percent, shared-pipe-upper-case, keep it separate, avoid putting it together
Feature-[feature-name]
Tag: type: feature
Type: lib (folder)
I strictly follow the DDD (Domain Driven Development) approach. There are 2 dimensions:
-
Horizontal: Each domain business has sub-domains/features. For instance, a customer domain has sub-domains/features: customer-profile, customer-list, customer-order. We have the structure below:
├── apps │ └── my-app └── libs ├── customer │ ├── feature-detail │ └── feature-list
-
Vertical: Inside each domain/sub-domains involves these parts:
- pipe
- directive
- service
- component
- constant
- helper
feature-name/ ├── constant/ // For feature-specific constants or enums ├── directive/ ├── helper/ // For utility classes or pure functions ├── pipe/ ├── service/ └── feature-name.component.ts // The main component
These ones are used internally or shared with sub-domains
Conclusion
Above, I shared my preferred Angular structure with Nx. Well-structured code is the “key” for building maintainable, scalable applications. In the next post, I will share my boundaries setup (Nx workspace). If you have another viewpoint or adjustment, feel free to drop a comment.
Top comments (0)