Angular2: CoreModule vs SharedModule
Could somebody explain what's a SharedModule
and a CoreModule
stand for?
I've been watching several project out there are using this approach in order to build its angular projects.
- Why do I need two modules?
- When should I import each one?
- Which
imports
,exports
,declarations
should each one have?
Solution 1:
TLDR:
-
CoreModule
should have onlyservices
and be imported only once in theAppModule
. -
SharedModule
should have anything butservices
and be imported in all modules that need the shared stuff (which could also be theAppModule
).
But:
Real-world modules are often hybrids that purposefully deviate from the [above] guidelines. These guidelines are not laws; follow them unless you have a good reason to do otherwise.
RTFM:
So, after going through the whole NgModules' docs (plus some other stuff to understand the context), I easily found the answer for your 2nd and 3rd questions there. Here are they:
SharedModule
- Create a
SharedModule
with thecomponents
,directives
, andpipes
that you use everywhere in your app. This module should consist entirely ofdeclarations
, most of them exported.- The
SharedModule
may re-export other widget modules, such asCommonModule
,FormsModule
, and modules with the UI controls that you use most widely.- The
SharedModule
should not haveproviders
for reasons explained previously. Nor should any of its imported or re-exported modules have providers. If you deviate from this guideline, know what you're doing and why.- Import the
SharedModule
in your feature modules, both those loaded when the app starts and those you lazy load later.
CoreModule
- Create a
CoreModule
withproviders
for the singleton services you load when the application starts.- Import
CoreModule
in the rootAppModule
only. Never importCoreModule
in any other module.- Consider making
CoreModule
a pure services module with nodeclarations
.
Although these are more detailed than the TLDR
version above and you now can go on and keep coding, it mentions some things that you have to read the docs to understand (i.e. 'widget module", "reasons explained previously", "pure service modules"), and it also doesn't answer your 1st question.
So let's try to do that!
Why do I need two modules?
You don't need two modules. This is what is in the docs:
The root module is all you need in a simple application with a few components.
With that said, it's important to make a different question first:
Why do people organize their projects into multiple modules?
The basic explanation for this comes right after the above statement in the docs:
As the app grows, you refactor the root module into feature modules that represent collections of related functionality. You then import these modules into the root module.
But you asked "why?" and this requires more than a basic explanation. So let's start explaining what are some problems that can happen if your app starts to grow and you don't separate functionalities into feature modules:
- The root module starts to become cluttered, with a code that is difficult to read and work with, since it must import all dependencies (e.g. 3rd party Modules), provide all services and declare all components, directives, and pipes. The more the app needs, the bigger this module will be.
- The different functionalities don't have a clear boundary between them, making it more difficult, not just to understand the app's structure, but also to have different responsibilities on a team.
- You can start having to solve conflicts between different parts of your app. For example, if you have directives or components that do the same thing in two different parts of your app, you'll either have to start using longer names to differentiate them or to rename them when importing.
Although you may think that the examples above are not exactly problems (maybe you work alone and can live with your own mess or all of your teammates are also messy), keep in mind that other people will certainly not agree with you and that's why you "have been watching several project[s] out there ... using this approach".
Considering that you agree with that and want to organize your growing app into feature modules, please note the following statements from the docs:
The root module and the feature module share the same execution context. They share the same dependency injector, which means the services in one module are available to all.
The modules have the following significant technical differences:
- You boot the root module to launch the app; you import a feature module to extend the app.
- A feature module can expose or hide its implementation from other modules.
Information to never forget: "services in one module are available to all [modules]", while other things, like Components, Directives, and Pipes must be injected in each module that wants to use them.
With this information we can now go closer to what you want to know by answering the following question:
Are all feature modules equal?
NO! At least it is suggested that they shouldn't be equal, as it is suggested that you shouldn't have just the root module, but you can do whatever you want. But you shouldn't.
The docs have a table showing the differences between the suggested feature module groups. Let's take a look on an excerpt of it:
FEATURE SHOULD HAVE SHOULD HAVE SHOULD HAVE
MODULE DECLARATIONS PROVIDERS EXPORTS
Domain Yes Rare Top component
Routed Yes Rare No
Routing No Yes (Guards) RouterModule
Service No Yes No
Widget Yes Rare Yes
ATTENTION! They make it pretty clear that this is a...
...preliminary guidance based on early experience using NgModules in a few applications.
So, again, you're not stuck to this guidelines and deviations can happen, but you should know what you're doing and why.
Now let's analyze this table:
- The
Service
andWidget
groups are the only ones that have completely different values between them in each column. - The
Domain
andRouted
groups are basically the same as theWidget
group, only with limited or none exports. - The
Routing
group is basically aService
group, with an export exception and limited to a specific provider.
So, let's consider the Domain
, Routed
and Routing
groups just variants of Service
or Widget
and focus on these last two.
Services should call your attention. Remember that you should never forget that "services in one module are available to all [modules]"? Well, if they call a feature module group Services
, that's because it must be isolated from the other modules so it can be imported only once. As an example, you can have an UserModule
that consists of services like SignUpService
, SignInService
, SocialAuthService
and UserProfileService
. Wherever you import that UserModule
, all of its services will be available app-wide. And as per the above table, it should not have declarations nor exports, just the providers.
Widgets sounds more generic, but it should tell you something. Remember that you should also never forget that "other things, like Components, Directives, and Pipes must be injected in each module that wants to use them."? So this is the type of module you will use for those. For example, you can have an UIModule
with ButtonComponent
, NavComponent
, SlideshowComponent
, HighlightLinkDirective
, CtaPipe
. And every time you need to use one or all of its exported elements, you import just the UIModule
.
So, basically, due to how Angular deals with Services, when you start splitting functionalities into feature modules, you have to isolate the services into their own module(s), while the other stuff can be organized between them as you wish.
How do CoreModule
and SharedModule
fit into this?
To keep it simple, the CoreModule
is a Service
module and the SharedModule
is a Widget
module. And that's why you should import the first only once in the AppModule
and the latter in all modules that need it. From my examples above, the UserModule
would be imported by the CoreModule
and the UIModule
by the SharedModule
.
But, as stated before, these are guidelines and deviations can happen, even in their own examples they declare components in the CoreModule
, but with an observation:
This page sample departs from that advice by declaring and exporting [in the
CoreModule
] two components that are only used within the root AppComponent declared by AppModule. Someone following this guideline strictly would have declared these components in the AppModule instead.
Personally, I think that the biggest confusion is regarding the naming choices. In general, people will think that everything that is part of your app's core (i.e. User stuff, NavBar, load bar, toasters etc) will go into the CoreModule
and everything that's shared across multiple features would go into the SharedModule
.
That's not actually true and a bit misleading, since all services are shared between all modules "by nature" and no service should be included in the SharedModule
, as well as a NavbarComponent
is part of the core of your app and no component should be included in the CoreModule
.
In any case, the recommendation is to follow the guidelines until you find a reason not to do so.
And here is the rest of the table above to help better understanding the guidelines:
FEATURE CAN BE SOME
MODULE IMPORTED BY EXAMPLES
Domain Feature, AppModule ContactModule (before routing)
Routed Nobody ContactModule, DashboardModule,
Routing Feature (for routing) AppRoutingModule, ContactRoutingModule
Service AppModule HttpModule, CoreModule
Widget Feature CommonModule, SharedModule
Cheers!
Solution 2:
I do use this approach myself and here's why/how :
(it's one approach and maybe other people will have != ideas which is fine)
I like to keep the app.module
as clean as possible. If you want to use universal or build your project with AOT (when not using angular-cli) you may need to have a duplicated app.module with small changes in all those files.
So if you import many modules into your app.module
, you'll have to maintain that list up to date in different files.
Here comes the core.module
:
Put every module you only want to import once here. Mainly, modules with forRoot
methods (the ones that exports their providers and that should be imported only once).
Import also your providers here. (if you use ngrx for example, declare your store here).
Then, the shared.module
:
Put every module you'll have to reuse across your app (CommonModule, HttpModule, RouterModule, MaterialModule, FlexLayoutModule, etc).
Finally, app.module
:
Import in app.module
your core.module
ONLY HERE. CoreModule should be loaded only once. In all your submobules, you can load the SharedModule
.
With that configuration, if you need to create another app.module for universal or others, you don't have to copy and maintain the whole module list in different files. Simply import the core.module
and you're good to go.
Solution 3:
As per style guide of Angular and my observations:
CoreModule(core.module.ts) Core feature module
All services that should be singleton should be provided here. For example
HelperService
,LoggerService
.
Application wide component should be declared in CoreModule
like header
, footer
.
CoreModule provides one or more singleton services. Angular registers the providers with the app root injector, making a singleton instance of each service available to any component that needs them, whether that component is eagerly or lazily loaded.
Only the root AppModule should import the CoreModule.
SharedModule(shared.module.ts) Shared feature module
declare components, directives, and pipes in a shared module when those items will be re-used and referenced by the components declared in other feature modules
It is suggested to avoid using services
in shared modules. Services are usually singletons that are provided once for the entire application or in a particular feature module.
Modules required by all feature modules should be imported in SharedModule like CommonModule
& FormsModule
.
Declaration of sharable component/pipe/directive should be in SharedModule. If these component/pipe/directive needs to be used by other feature module, they must exported.
If using Material, it's best place to import and re-export Angular Material components.
References: CoreModule, SharedModule