DEV Community

Cover image for Angular Architecture - Organizing modules
Dino Dujmovic
Dino Dujmovic

Posted on • Edited on

Angular Architecture - Organizing modules

Are you among the many who find themselves wondering about the most effective approach for organizing folders and files in Angular applications? The answer may be right in front of you, "hidden" in plain sight.

"Do structure the application such that you can Locate code quickly, Identify the code at a glance, keep the Flattest structure you can, and Try to be DRY (Do Not Repeat Yourself)." - Angular Style Guide

... and working with Angular will be like playing with LEGO blocks.

My goal for this series is to establish a consistent mindmap and clear starting point for each application I work on and to achieve early satisfaction in the development process.

hr

🎯 Starting point

You probably noticed that almost every web application has a standard page layout that includes common UI elements such as the header, footer and possibly a navigation menu, as well as routes that define the path for different pages (features, views) of the application.

standard page layout example

These components are often referred to as "shell" components and are typically designed to be rendered only once per page load meaning that we will import them inside of our app component (root).



// app.component.ts
@Component({
    selector: "app-root",
    template: `
        <app-header></app-header>
        <app-menu></app-menu>
        <main>
           <router-outlet></router-outlet>
        </main>

        <app-footer></app-footer>
    `,
})
export class AppComponent {
}


Enter fullscreen mode Exit fullscreen mode

Note:
router-outlet can be described as a placeholder that displays the page (features/views/modules or whichever naming you prefer) components associated with different routes defined in Angular application - in this case inside of app-routing.module

hr

🎯 Folder structure

starting folder structure

My apps will usually consist of these 3 folders (core, pages and shared) as well as app module file.
It's worth noting that some developers prefer to use alternative folder names to page, such as features, modules, or views. Others may choose to flatten the folder structure entirely and place all routable modules at the root level of app folder. Ultimately, the folder structure should be organized in a way that makes the most sense for the specific project and team.

hr

🎯 App module

While the AppModule is responsible for bootstrapping the application, it's generally best to keep it as small as possible and delegate most of the application logic to other modules, such as the CoreModule or page modules.



// app.module.ts
@NgModule({
    declarations: [
        AppComponent
    ],
    imports: [
        AppRoutingModule,
        CoreModule
    ],
    bootstrap: [AppComponent]
})
export class AppModule { }


Enter fullscreen mode Exit fullscreen mode

In this case, the main responsibility of the AppModule would be to import and define the main app routing.
Because we created each page as a module, it can be lazily loaded.



const routes: Routes = [
    {
        path: "",
        pathMatch: "full",
        redirectTo: "/"
    },
    {
        path: "",
        data: { preload: true },
        loadChildren: () => import("@pages/home/home.module").then((m) => m.HomeModule)
    },
    {
        path: "movies",
        data: { preload: false },
        loadChildren: () => import("@pages/movies/movies.module").then((m) => m.MoviesModule)
    },
    {
        path: "settings",
        data: { preload: false },
        loadChildren: () => import("@pages/settings/settings.module").then((m) => m.SettingsModule)
    },
    { path: "**", redirectTo: "/" },
];

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


Enter fullscreen mode Exit fullscreen mode

Note the data preload property passed to the every route. This is cool technique you can use by creating custom preload modules strategy.



@Injectable()
export class PreloadModulesStrategy implements PreloadingStrategy {
    preload(route: Route, load: () => Observable<any>){
        if (route.data && route.data.preload) {
            return load();
        }
        return of(null);
    }
}


Enter fullscreen mode Exit fullscreen mode

With this you have the ability to choose which pages will be automatically preloaded and which ones will only be loaded when the user lands on a specific route.

🎯 Page modules

page modules

I will write about the content of page modules in the future but for it's clear that:

  • our top level modules are referenced inside of application route
  • page modules are organised into their own folder
  • it is easy to find everything related to a page such as subpages, components

🎯 Core and shared modules (folders)

core and shared modules

Core module

The CoreModule is usually imported only once in the root module of an Angular application, and it is typically not imported into any other module. By doing so, the CoreModule ensures that the Angular's services and components it provides are only created once and are available to all components and modules within the application.

One of the techniques you can use to ensure that CoreModule is imported only once is by creating a guard.



// guards/ensure-module-loaded-once.guard.ts

export class EnsureModuleLoadedOnceGuard {
    constructor(targetModule: any) {
        if (targetModule) {
            throw new Error(
                `${targetModule.constructor.name} has already been loaded. Import this module in the AppModule only.`
            );
        }
    }
}


Enter fullscreen mode Exit fullscreen mode


// shell/layout.module.ts

@NgModule({
    imports: [CommonModule, RouterModule],
    exports: [HeaderComponent, FooterComponent],
    declarations: [HeaderComponent, FooterComponent]
})
export class LayoutModule extends EnsureModuleLoadedOnceGuard { 
    constructor(@Optional() @SkipSelf() parentModule: LayoutModule) {
        super(parentModule);
    }
}


Enter fullscreen mode Exit fullscreen mode


// core.module.ts
@NgModule({
    imports: [
        HttpClientModule,
    ],
    exports: [
        BrowserModule,
        LayoutModule,
    ],
    providers: [
        PreloadModulesStrategy,
        { provide: HTTP_INTERCEPTORS, useClass: HttpInterceptor, multi: true }
    ]
})
export class CoreModule extends EnsureModuleLoadedOnceGuard {
    constructor(@Optional() @SkipSelf() parentModule: CoreModule) {
        super(parentModule);
    }
}


Enter fullscreen mode Exit fullscreen mode

Some examples of what might be included in a CoreModule are:

  • Shell components (as header, menu, footer)
  • Logging and error-handling services
  • Data services (for API calls)
  • Interceptors for HTTP requests and responses
  • Store / application state (like ngrx, ngxs, custom observable store)
  • Global configuration settings
  • Models/interfaces/classes
  • Helper and util classes, methods ...

Services that are specific to a page module can be placed within that module, but it's often better to delay moving them until you're sure they are only used by that specific module.

core folder

Shared module

The shared module is typically used to contain presentational components, directives, pipes that can be imported and shared across different modules.

If you look into some examples of typical presentational a.k.a dumb components:

you may notice that these components do not possess any application logic and are not doing actions like such as API calls or being subscribed to state observables. Instead, they obtain data and event handlers as inputs from their parent or container components.

hr

Top comments (2)

Collapse
 
spahicharis profile image
Haris Spahic

Great article. Maybe call it Angular Structure instead!?
In any case, this is almost identical structure I use all the time (when ever starting from the scratch). With some small changes to folder naming: @core, @shared. Just to have it on top.
Also, prefer to use “Angular libraries” when ever I can.

Will be following next article from you. Cheers mate.

Collapse
 
mamiapatrick profile image
MAMIA Patrick

hello @spahicharis can you share a public repositery where it is possible to see your organization on a project on how everything is related. Unfortunaletly the codebase of this example is not available with the complete codebase..
Thanks