What is SharedModule anyway?
To set up some context for this post, here’s a typical SharedModule
1@NgModule({2 declarations: [ComponentA, ComponentB, ComponentC, ComponentD],3 exports: [ComponentA, ComponentB, ComponentC, ComponentD],4})5export class SharedModule {}Or more than often, we usually have a SharedModule for re-exporting 3rd party Modules
1@NgModule({2 imports: [MatButtonModule, MatTableModule, MatCardModule],3 exports: [MatButtonModule, MatTableModule, MatCardModule],4})5export class SharedMaterialModule {}With this in place, we can import SharedMaterialModule to have access to MatButtonModule, MatTableModule, and MatCardModule.
Now that we have an idea of what SharedModule is, let’s explore why SharedModule is not as clever as we thought it would be.
Scenario 1: Internal SharedModule
Problem
- Lazy-load
FooModule - Lazy-load
BarModule SharedModuledeclaresSharedAComponentandSharedBComponentFooModuleusesSharedAComponentby importingSharedModuleBarModuleusesSharedBComponentby importingSharedModule
A screenshot showing bundlesize of FooModule, BarModule, and SharedModule
From Screenshot 1, we can see that SharedModule is separated into its own chunk. This makes the size of FooModule and BarModule small, which appears to be a good thing.
However, both FooModule and BarModule are lazy-loaded modules. It means, in practice, our users might land only on FooModule (eg: /foo) and never need to load BarModule (and vice versa).
When we load either FooModule or BarModule, we load SharedModule.
Why is this a bad thing? Because FooModule only uses SharedAComponent, but we also load SharedBComponent since we load the whole SharedModule.
This is wasted! Imagine having EVERY shared components in SharedModule, that would be a huge waste. In other words, it kind of defeats the purpose of your lazy-loaded modules in my opinion.
Solution
The easiest solution is to apply Single-Component-as-Module (or SCAM) here for SharedAComponent and SharedBComponent. Learn more about SCAM
A screenshot showing bundlesize of FooModule, BarModule, with two SCAMs
After we apply SCAM to the two shared components, we will end up with Screenshot 2. FooModule bundle size is getting slightly larger because SharedAModule is bundled together with it (the same applies to BarModule and SharedBModule).
However, when our users land on FooModule, we will truly only load what the users actually need: FooModule and SharedAComponent without loading the unnecessary SharedBComponent
Scenario 2: External modules
This scenario is somewhat out of our control but we will explore it as well.
- Only
FooModuleutilizesFormsModule - Both
FooModuleandBarModuleutilizeFormsModule
A screenshot showing bundlesize of FooModule and FormsModule
A screenshot showing bundlesize of FooModule, BarModule, and FormsModule
Here, you can see that if only FooModule needs FormsModule, then Angular will know to include FormsModule together with FooModule bundle, so that if our users land on BarModule, we don’t need to load FormsModule for them. (Screenshot 3)
On the other hand, if both FooModule and BarModule need FormsModule, then Angular will separate FormsModule into a common chunk so FormsModule is only loaded once. (Screenshot 4)
Conclusion
Main take-away is that Angular is smart enough to help you enhance the users’ experience with your application. Embrace Single-Component-as-Module, you’ll only pay for what you (or your users) need.Main take-away is that Angular is smart enough to help you enhance the users’ experience with your application. Embrace Single-Component-as-Module, you’ll only pay for what you (or your users) need.