TLDR:
CoreModule
should have only services
and be imported only once in the AppModule
.
SharedModule
should have anything but services
and be imported in all modules that need the shared stuff (which could also be the AppModule
).
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.
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 the components
, directives
, and pipes
that you use everywhere in your app. This module should consist entirely of declarations
, most of them exported.
- The
SharedModule
may re-export other widget modules, such as CommonModule
, FormsModule
, and modules with the UI controls that you use most widely.
- The
SharedModule
should not have providers
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
with providers
for the singleton services you load when the application starts.
- Import
CoreModule
in the root AppModule
only. Never import CoreModule
in any other module.
- Consider making
CoreModule
a pure services module with no declarations
.
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
and Widget
groups are the only ones that have completely different values between them in each column.
- The
Domain
and Routed
groups are basically the same as the Widget
group, only with limited or none exports.
- The
Routing
group is basically a Service
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!