Angular Architecture - Organizing modules
Tags: angular, architecture, frontend
Published: 2023-04-18 22:08:48

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](https://angular.io/guide/styleguide#application-structure-and-ngmodules)

... 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p2xe9kuogtn0ee8zcobn.png)


#### 🎯 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xs2faxqncqvpjr4c6br1.png)

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

```typescript
// 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 {
}
```

_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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p2xe9kuogtn0ee8zcobn.png)


#### 🎯 Folder structure
![starting folder structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/da21bxfzqpdj3fgy9hqq.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p2xe9kuogtn0ee8zcobn.png)

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

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

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. 

```typescript
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 { }
```

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

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

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h0taj8shjjjwlzefbod9.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uz5scy8314l7pzry1m98.png)

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

```typescript
// 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.`
            );
        }
    }
}
```

```typescript
// 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);
    }
}
```

```typescript
// 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);
    }
}
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2ki5ng5sfa9ff5pwe6wh.png)



##### 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:
- [Angular Material Components](https://material.angular.io/components/categories)
- [Angular Bootstrap Components](https://valor-software.com/ngx-bootstrap/#/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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p2xe9kuogtn0ee8zcobn.png)