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.

  1. Why do I need two modules?
  2. When should I import each one?
  3. Which imports, exports, declarations should each one have?

Solution 1:

TLDR:

  1. CoreModule should have only services and be imported only once in the AppModule.
  2. 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.


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 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!

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