What are Feature Modules?
A feature module is an ordinary Angular module for all intents and purposes, except the fact that isn’t the root module. Basically - it’s just an additional class with the @NgModule
decorator and registered metadata. The feature module partitions areas of the application and collaborates with the root module (and with further feature modules).
The main aim for feature modules is delimiting the functionality that focuses on particular internal business inside a dedicated module, in order to achieve modularity. In addition, it restricts the responsibilities of the root module and assists to keep it thin. Another advantage - it enables to define multiple directives with an identical selector, which means avoiding from directive conflicts.
There are five types of feature modules: Domain, Routed, Routing, Service and Widget. Each of them concentrates and provides a particular type of utilities.
So as to decide which type to use - we have to answer the following:
- Does the module have declarations?
- Does the module have providers?
- What’s the top component which should be exported - if any?
- Does the module have exports at all?
- Which modules are intended to import this module?
During the article we’ll criticize these touchstones.
Explaining the Application
Let’s assume we’re an organization that develops a product for recruitment of applicants for “Hero” job.
As a staffing provider of “Heroes” - our application provides the capabilities:
- A dashboard with the most popular applicants and a search input.
- A list of all applicants.
- A detailed view of specific applicant.
Take a look at our application in action:
Note: This application is part of “Tour of Heroes” tutorial.
In our organization, the application source code is maintained by three dedicated teams:
- “Dashboard” team - has responsibility for the dashboard view.
- “Heroes” team - has responsibility for the list view and the detailed view.
- “Infrastructure” team - has responsibility to maintain the application skeleton, fetch the data and provide a general search component.
In the next steps, we’ll explore the source code and make dedicated feature modules for each team.
Diving into the Source Code
To better understand of our application, we shall take a look at the source code:
At first, we can notice that there are two modules - AppModule
(the root module), AppRoutingModule
(its routing module). The root module includes all necessary stuff in a tightly coupled and flat manner. Actually, this is a monolithic application structure.
As we know, @NgModule
decorator takes a metadata object that tells Angular how to compile and run the module. In our example, it takes:
We want a structure which is simple to maintain, test and deploy. In this case, it seems that any future change, as little as it, may be risky. Moreover, we’d not like to test and deploy whole application at changes of a very specific area.
Currently, our AppModule
declares all components and provides the HeroService
. Also, it imports an extra feature modules of Angular - FormsModule
and HttpModule
. We’d like that will be modules which declare, provide and import stuff as required specifically.
Thus, the thing we’re going to do is creating five new feature modules: DashboardModule
, HeroDetailModule
, HeroesModule
, CoreModule
and SharedModule
.
Each is capable to export public utilities for other modules. In addition, The first three will have an internal routing module (that provides the routing configuration).
Building the Dashboard Module
Our first feature module will be a domain module that’s under the auspices of the Dashboard
team. Let’s begin with a job.
As we remember, the application contains a route which exposes the dashboard Component
. The application redirects to that route initially.
So, at first, we need to create a new directory for the feature module inside “app” directory. Obviously, we’ll name it “dashboard”. Then, we’ll move the dashboard Component
into it.
As a result, we’ll have to move the relevant route from “app-routing.module” into a new module routing file - which is named “dashboard-routing.module”:
Besides, we’ve to move the declaration of dashboard Component
from “app.module” into a new feature module file - which is named “dashboard.module”:
As it might be seen, this module doesn’t contain any providers and it exports the DashboardComponent
- which is used as the top component.
The last thing we should do is importing of DashboardModule
in the root module:
In the end, we’re getting this as part of the directory structure:
Building the Heroes’s Modules
Now, let’s build domain feature modules for “Heroes” team.
These steps are pretty similar to the building of “Dashboard” module, so we’ll cover those quickly. If you feel you understood the point of domain modules, skip to the shared modules part.
First, we create two new directories inside “app”: “hero-detail” and “heroes”. Second, we should create a module routing file for those directories, and move the appropriate routing business for both.
Here’s an example for “hero-detail-routing.module”:
An example for “heroes-routing.module” as well:
Thereafter - we’ll take the relevant Component
declarations for each actual module file.
For instance, “hero-detail.module” is likely to be:
The next will be “heroes.module” in a similar way:
After an exhausting technical job, the directory structure apparently includes:
Building the Shared Modules
So far we built domain features modules for the sake of particular page view purposes. Perhaps you’ve noticed, HeroService
and Hero
interface are being used in a couple of places. Furthermore, we’d like a general place for common utilities, such as the search Component
.
This is the time to dismantle the shared code into feature modules which will be maintained by our “Infrastructure” team. To deal this demand, we’ll build a service and widget feature modules.
Let’s start with the service feature module and name it “core”. It’s going to be a module which contains all providers but doesn’t have declarations or exports at all. It’ll be imported by AppModule
solely.
As a beginning, we should make a directory inside “app” that is named “core”. Now, we’ll move into that directory the providers - HeroService
and HeroSearchService
.
As far we know, a feature module requires a module file. Here’s attached “core.module”:
Be aware to import this module inside AppModule
. Also, we should remove the injections of providers in anywhere else. Actually, we’ve finished with CoreModule
.
Our last feature module will be a widget type, which is named “shared”. As a widget module, it declares general stuff such as components, directives and pipes, and exports them as well. Be informed that it doesn’t include any providers. Practically, it includes the search Component
and Hero
interface. This module is going to be imported by whatsoever module which needs the utilities. In order to simplify, let’s assume that all modules depends (or may depend) on this one.
As usual, the first step is making a new directory (“shared”) inside “app” and moving into it the relevant stuff. Considering the fact that is a module with common stuff, we’ll move the ‘hero-search’ Component
and Hero
interface which were mentioned before.
Of course, we supposed to create the “shared.module” file as well:
Important to note: SharedModule
exports for us Angular utility modules such as CommonModule
and FormsModule
, so - we get them for free and we don’t have to import them again in the actual places.
Eventually, the directory structure includes:
Conclusion
In this article, we saw how we can restructure an Angular application using feature modules. At first, we explained what is the concept of feature modules in general. Thereafter, we dived into the source code of a predefined monolithic application.
We built three domain feature modules with specific view components. Moreover, we built a service feature module with the providers and a widget feature module for utilities.
After all steps, here’s our final result:
As a forward step, we can better the project modularity by extracting the feature modules as an isolated scoped-packages. Also, we can make an internal barrel file for each module.