All Blogs

A closer look at SharedModule

What is SharedModule anyway?

To set up some context for this post, here’s a typical SharedModule

@NgModule({
    declarations: [ComponentA, ComponentB, ComponentC, ComponentD],
    exports: [ComponentA, ComponentB, ComponentC, ComponentD],
})
export class SharedModule {}

Or more than often, we usually have a SharedModule for re-exporting 3rd party Modules

@NgModule({
    imports: [MatButtonModule, MatTableModule, MatCardModule],
    exports: [MatButtonModule, MatTableModule, MatCardModule],
})
export 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

A screenshot showing bundlesize of FooModule, BarModule, and SharedModule Screenshot 1. Bundle size 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 Screenshot 2. Bundle size of FooModule, BarModule, SharedAModule, and SharedBModule

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.

A screenshot showing bundlesize of FooModule and FormsModule Screenshot 3. Bundle size of FooModule and FormsModule

A screenshot showing bundlesize of FooModule, BarModule, and FormsModule Screenshot 4. Bundle size 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.

Published on Tue Aug 23 2022


Angular