What Does Modular Architecture in Angular Even Mean?
Decrypting the mysteries of modularity
The idea of modularity gets thrown around a lot, especially in the space of clean and structured code. It’s one of those ideas that’s just supposed to implement itself if you repeat it enough in a sentence.
When it comes to Angular, achieving modularity is more than just containerizing your code. When we start coding or learning how to use Angular, we often jump in head first — YouTube-tutorial-styled and right into the deep end. If it works, it works, and we often just leave it at that.
However, modularity is the embodiment of a thinking process. These thoughts and processes trickle down into the code that it’s supposed to represent.
So how do you think in a modular manner when it comes to Angular?
Start With the Problem Domain
The issue many developers face is the lack of understanding of what code really is by business owners and managers. To them, code is an intangible concept that exists with a mysterious aura. It is also an ends to a means — with the ends often starting without defining what the means are.
A problem domain is a definition of what you need in order to solve a particular issue. It is a scope for your work, a clear boundary line around what is relevant and what is not. It is the thing that gives us Point A and Point B — the software produced is the connecting line between these two points.
When we don’t have a clear problem domain, we don’t have a clear picture of where we want to go with our code or how to build things. It’s like constructing a house without the architectural plans and just giving the builders some wood and tools to work with. You may end up with a house, but the structural integrity of the building will not be at the same level as one built with a plan and on spec.
When we start with the problem domain, we are able to identify each component in a system and abstract them out as necessary.
But how do we define a problem domain? Let’s take a look at the hypothetical example below.
Presenting Our Fake Business: Gym Monkeys
Your friend Andy needs a gym membership system. He knows that he wants members to be able to sign up online, enter their details and then book in an induction. He also wants gym instructors to be able to access member details and set up nutrition and training plans for a member’s training program.
Members can only access their own information and no one else’s. There is also an admin that is able to add gym instructors in as staff.
On the surface, this sounds like a rather simple and basic system — however, it can grow to be unnecessarily complex if it’s not structured with clear domains. In the brief above we are able to identify two things: roles and actions.
There are three roles — admin, members, and gym instructors. There are also four main actions — the ability to sign up, book an induction, access details, and set up plans.
Let’s take a look at them in a visual format.
Now that we have all the necessary components laid out, it’s time to create relationships between them.
The problem domain is essentially the reason why we’re building our software. In this case, it’s to create a system that can handle and translate Andy’s ideas. What we’ve done here is change the format of Andy’s description of the problem domain into a systematic approach toward visualizing what the connectors between points A and B look like.
Many often skip this part and jump right into the coding. This leads us to just wing our code into existence without much planning or confirmation of the translation with Andy.
While in theory, we could have just created this image in our heads, for bigger and much more complex systems, where there are multiple people working on a team, this process of defining the problem domain clarifies what is needed and the component parts required to make our modules.
What Exactly Are Modules?
At its simplest, a module is a defined piece of code that sits in your system. How well it is defined depends on how well you managed to translate out your problem domains.
In theory, you could have all your code inside one module, but that wouldn’t be the most efficient way to do it.
Modular programming is a technique that deals with the separation of functionality into independent and interchangeable parts. Code is not Play-Doh — nor is it a jigsaw puzzle. Rather, it’s more like Lego blocks if modules are implemented correctly.
The thing with Legos is that while parts can easily fit into one another, they can also come in different sizes and types. Some parts can only be connected once, while others may have a special-featured shape like a slope.
While the pieces are standardized, the variation is the thing that gives us the ability to build in a variety of combinations. How well it sits structurally depends on the kind of pieces used and their connection with each other.
It is the same concept with modules.
In Angular, there are two official types of modules — root modules and feature modules. However, we can further break down the feature modules into their specialized parts, such as routing, imported, component, services, and widget modules.
Stripped down to their most basic parts, all Angular modules look the same. The only difference between them is their purpose for existing. The scope of this existence determines how big or small your module ends up.
Every Angular module has an import, the
@NgModule decorator, and an exported class. The
@NgModule decorator tells Angular that the class being exported is an Angular module. Imported modules are expected to be used inside the class at some point, and the class is the equivalent to a Lego block.
Demystifying Different Types: When and Where to Use Them
If you used the CLI to generate your Angular app, the generator would have created the bare-bones application with a root and an
app.component.ts file that houses your main application module.
The root — or
app.module.ts — is the file that kickstarts the process of running your Angular app. Everything begins in this file, and without it, your Angular app won’t be able to run.
In the root module, it will contain all the imports that link back to everything you’ll end up using. Think of it as a library to provision future resources. It gives your application the ability to call on them for usage when they are needed.
Then we have the feature module, aka the rest of your Angular application. However, feature modules are not single faceted and are further broken down in different categories based on functionality.
To have modular code is to group a particular idea together. To construct a modular architecture is to create code that satisfies the problem domain in a way that the code contains a standardized way of connecting with each other while remaining independent at the same time.
There are multiple ways to grouping ideas, and in Angular’s case, the suggested scope for modules are based on sets of certain characteristics and functionality. Here is a quick summary of the different types, when to use them, and why.
The idea behind a routing module is to centralize all your routes in one place rather than have them spread out across multiple files. When it comes to routing, you’ll usually only have one file or multiple that are split into types of links that are based on roles.
This method of splitting based on roles is not definitive but is a good way to group your information rather than having one massive file cover everything.
A routing module essentially covers movement through views and obtainsdata from the address bar. It doesn’t deal with declarations or have any responsibility beyond this scope.
Officially, it’s called a routed module. However, many of us know it as the module that has an associated
css file with it.
If you’ve used the CLI to generate your components, the command looks something like this:
ng generate component <componentNameHere>
This kind of module is the type where we often put everything in because due to a technicality, you’re allowed to. But on a best practice perspective, this isn’t the best thing to do.
A routed module is the target for a route. Its purpose is to act as the aggregator module that brings all your other modules together in one space. They’re not reused by any other modules and are more like the glue that puts all the parts together behind the scenes for the presentation view to display as needed.
This means that the stuff inside a routed component module is never consumed by another module — because this is where the scope and purpose of a routed module ends.
In the case of our fictional business Gym Monkeys, routed modules implement the relationships between the different roles and actions.
A domain module deals with a particular user experience that is repeated in different routed modules. For example, a particular form — like a sign-up form — appears more than once across your application.
When this happens, you can abstract the experience out to standardize the code. This gives you the ability to import it into your routed modules. They don’t usually have a provider — aka something that deals with data — and are usually considered dumb by design.
If they do have providers attached, the life cycle of that provider exists at the same length as the domain module. This means that if the domain module is terminated because of a routing change, it’s not the end of the world.
A service module deals with data. They are essentially a collection of providers that have nothing to do with views. They are utility-based and often deal with connecting the app with external data sources.
For example, if you wanted to submit the sign-up form to an external API, you wouldn’t do it in the routed or domain modules. Rather, you’d extract it out and put it into a service module for future and untethered consumption.
Widget modules are third-party components, directives, and pipes. They are similar in concept to a domain module, except they are not native to the application.
Rather, they are often imported into the project as supplementary libraries. For example, UI component libraries are often built as widget modules because they are distributed independently and do not rely on services to work.
At the end of the day, modular code is essentially your ability to arrange information in a categorical way that makes sense for the size of your application. There is no bulletproof one-method-fits-all kind of deal. and one method of thinking may be too convoluted for small applications or too restricted for larger applications. When this happens, refactoring is inevitable.
Modular architecture in Angular is the act of installing a particular separation of code from each other based on the scope and problem domains.
The act of installing such an ideology on your code can be daunting at first, but with practice, it becomes easier to identify and implement. Like anything in code, there is always a learning curve. When it comes to modular architecture, the ideas in Angular are also transferrable to other frameworks and libraries.
This is because the concept of modularity is not something new and exclusive to Angular. Rather, the framework’s structure forces you, as much as it can, to work in such a manner. Despite this, there is still code in the wild that breaks this ideology by blurring the lines between the different types of modules.
However, I hope that the explanations above have cleared up some things and helped you with the concept of implementing a modular architecture in Angular.
Thank you for reading.