An Overview of Module Organization in Angular
Angular Modules serve as the organizational backbone of an application, grouping components, directives, and pipes into cohesive units of functionality.
A thoughtful module strategy pays off in cleaner code, optimized bundle sizes, and simpler maintenance. A poor strategy, by contrast, can create tangled dependencies and unnecessarily heavy bundles.
This post walks through several organizational approaches and offers guidance for selecting one that fits your project.
Contents
- Executive Summary
- Sample Project
- Evaluation Criteria
- Approaches
- Conclusions
Executive Summary
To compare module strategies, I look at metrics such as bundle weight, adaptability, clarity, and repetitive code.
The key takeaway is that no single approach suits every project, though the discussion below helps you weigh the trade-offs to make an informed choice.
Sample Project
This guide demonstrates how I organize a typical application. It’s not a trivial demo or a massive enterprise system, so the structure might not align perfectly with your project size, but it serves as a solid foundation for new work.
Our sample is a music player—a familiar scenario. It includes a persistent player along the bottom and several screens for discovering and exploring tracks.
Directory Layout
Before diving into modules, let’s consider how to lay out the codebase.
Directory structure and code organization are matters of taste and depend on the specific project. Treat this as a source of ideas rather than a strict template.
I rely on three top-level directories:
- core
- shared
- views
The views folder contains the three routable screens:
- home-view
- search-view
- album-view
The core folder holds everything that must be present at startup, such as the main layout, the bottom navigation, the mini-player, and the expanded player view.
The shared folder contains components that appear across multiple screens. In this example, there are two:
- horizontal-albums
- main-title
There’s also a category I didn’t list initially: view-specific components. While some elements are reused, others belong to a single screen. Placing code close to its usage is generally wise—so components tied to one view live inside that view’s folder. In this project, the search-bar is only used by the search screen, so it stays there.
Leveraging Angular Modules
Angular Modules let you bundle those components into logical groups. There is no strict right or wrong way to partition an app—many options are viable.
Evaluation Criteria
A few considerations should guide your choice of module structure.
Bundle size is directly influenced by how you split things up. This also applies to the size of individual lazy-loaded chunks, which affects how quickly the browser can load your application.
Scalability speaks to how easily you can navigate and modify your code. Certain module splits make later reorganization more challenging.
Simplicity reflects how easily your team understands where and when to create a module.
Boilerplate refers to the extra code required for each module. More modules naturally mean more boilerplate, though generators can ease this burden.
Testability gauges how straightforward it is to test a component in isolation. If a component sits in a large module, you may need to mock many services.
Approaches
There are 3 strategies worth examining:
- Everything in one module
- One module per feature / view (Lazy Load)
- One module per component (SCAM)
Each approach comes with its pluses and minuses; the radar chart gives a visual comparison.
Single module for all components is straightforward. When you create a component, you simply add it to the declarations of app.module.
Although it is simple, this method has notable downsides:
- All the components load greedily, extending the initial load time
- The
app.modulefile grows large, and every refactor involves editing that file - Tracing dependencies becomes tricky. For instance, it’s difficult to identify which code becomes unused after deleting a component, potentially leaving orphaned pieces behind.
This approach might work for small apps, but I would not advise it for bigger projects.
Single component module (SCAM) takes the opposite route. Here, every component gets its own module. SCAM stands for “Single Component Angular Module,” a term introduced by Lars Gyrup Brink Nielsen in this article.
I appreciate this style because it clarifies what each component depends on. Each component module imports only what it directly uses, so removing a component also removes its dependencies. That’s a nice win.
Another advantage is that Angular is able to produce the most efficient lazy-loaded bundles thanks to the well-defined dependency graph.
Finally, the guideline is easy to articulate: every component has its own module. That removes any debate about how to structure the modules.
The one concern is the added boilerplate, since there are now more module files and you must list all dependencies explicitly. This may become less of a problem with the forthcoming standalone component API on the @Component decorator that eliminates the need for NgModules. Watch that Github spike for details.
Module per view or feature (with lazy loading) is likely the most traditional pattern.
You achieve the best bundle size when each view is loaded lazily. Given that the shared module is used across multiple lazy-loaded chunks, it is placed into its own common bundle. I’ve covered how Angular forms these bundles in a separate post: Bundling Angular Modules.
This pattern sits between the other two. You get the benefit of smaller bundles, less boilerplate, and a well-defined structure.
In larger apps, you can refactor by splitting into smaller feature modules or bundling several views into bigger chunks. That can help with organization, but it may also create ambiguity about when to introduce sub-modules. The official Angular documentation is a great reference for this pattern.
Choosing a Strategy
The simplest call is to avoid the single-module route. If your app has routing and more than 10 components, steer clear of that approach.
When deciding between view/feature modules and the SCAM pattern, ponder this:
How much extra boilerplate are you willing to accept in return for a clearer dependency graph and an easy-to-follow rule?
I find the standard feature/view module to be the right default, so long as the number of imports and declarations stays manageable.
For libraries, SCAM is an excellent fit—consumers can import only the specific components they need. It also helps when a cohesive strategy is hard to pin down, since the rules are so direct.
Regarding performance, both SCAM and feature/view modules lead to the same bundle structure. They are excellent choices, as long as you don’t accidentally import a feature module into your app or core module—a mistake SCAM does not readily allow.
Key Takeaways
This guide walked you through different module approaches and their trade-offs regarding bundle size, scalability, simplicity, and boilerplate — the factors that matter most when picking a method.
The three approaches highlighted here are the ones I encounter most often. If your project uses a different pattern, let me know — this post may get an update soon with a fresh strategy 🙃
Enjoyed the read? Share it around and connect with me on Twitter for more web dev content.
Spotted a typo? Help refine the post by opening an issue here or drop your thoughts here.










