Module SCAM Pattern
The Angular Module SCAM pattern
Working with Modules can be challenging, especially in large Angular applications. We often find ourselves having to refactor, for a variety of reasons, and this inevitably leads us to our modules. And then, we hit this pain point:
Let's use a hypothetical situation with the above. The shopping cart component needs to be used in a new section of our application, not an uncommon sort of need. So we make a new module, and we declare that Shopping cart inside, and set up the appropriate routes.
Well, that was hopeful - but this doesn't work. Our Shopping Cart component actually has a few dependencies! Okay no problem, we know that we can check the constructor of a component to see service dependencies, so let's check that.
Note: We are going to assume for the sake of this example that all the services we are going to reference are provided through our providers: [] fields in our modules, but the recommendation is to avoid this pattern as much as possible - take advantage of Angular 6+'s providedIn: "root" property, available to you in @Provider decorators.
Ah! That ConfigService
that was previously provided to our CustomerPortalModule
is what we need to get this all working, no problem, we can add it in like so:
I'll save you the suspense, this still isn't working! After digging, we see that in fact, the ConfigService
relies on the PromotionService
, so we include that. Still no luck.
The next thing to check would be our templates, any components and pipes declared in our ShoppingCartComponent will need to be imported into this new Module, so we do:
You guessed it, still no luck! That is because our recently added CustomerCard
component also relies on its own dependencies to be provided in any module it uses. I'll end this here, but you can see, this becomes an unending chain of pain.
Often, this is why you see a SharedModule
pattern utilized so frequently in our Angular Application. If we dump all of our application dependencies in one place, this problem goes away, and we can just pass around a giant shared module, exporting all of its contents and providing them to every Module that needs it.
This kinda works. However, anyone experienced with something like this also understands that the pain of a giant shared Module is just trading in one kind of discomfort for another, and it has a pretty bad code smell to it.
The solution to this problem, actually, ends up being quite simple with the SCAM Pattern.
What is the SCAM (Single Component Angular Module) pattern in Angular?
I read this amazing blog post a few years back that changed how I write my angular applications.
I recommend reading it and giving the author kudos, because it is fantastic and concise. That being said I will also give a breakdown here to help explain the idea.
Essentially... why not make everything have it's own custom Module? We use Modules to encapsulate similar code, but with the patterns above, we end up in a place where we throw this idea out the window and end up having to work around Angular Modules, just to get our applications running. However, let's try to reimagine the above using the SCAM.
Again, because I think it's so important to emphasize - for the sake of this example, I will keep the providers imported through the module declarations, however it is even simpler when you use the ProvidedIn: "root" pattern
Everything we need for our CustomerCard
Component is declared in this module. It's very simple, so you might think 'why bother', but the SCAM pattern relies on consistency to get the most out of it, and in the end, you'll be happier for it. You can see we also export the Component, and since we don't need this pipe used anywhere else, we sort of lock it behind this module.
Before we move on, Let's go a step further - let's make a module for the CustomerFormatterPipe as well, just IN CASE we need this in the future.
Then we update our CustomerCardComponentModule
like so:
Now, understanding that pattern, lets assume we follow it through for everything else that we have. For example, our ShoppingCartComponent
!
Now our original CustomerPortalModule
looks like this:
Suddenly, It's actually much clearer What the dependency tree looks like for this module. The CustomerPortalComponent
really only needs exports from the component library, and the ShoppingCart
, giving you at a glance, a much better idea of what this slice of our application is consuming. Then... what does our ReorderPortal
look like?
Now, this is all we need to have our Shopping Cart component usable in our ReorderPortal
. No fuss, no muss.
Last updated