Module scoped Services in Angular
Angular 6 brought some really neat changes around it’s module and packaging system. I realize that when Ivy is released, some of this will become obsolete, but I would be willing to bet that the concepts of it will not be.
A bit of back story…
As web application development has evolved over the years, the amount of code and assets, or data loaded onto the client to provide the functionality of the application itself has vastly increased. It was out of this that we started seeing solutions around how to prioritize, or bundle, your application in chunks so that the client does not have to download so much data at the start. This practice was once something that required a varying amounts of extraneous work or knowledge for developers. You would probably end up having to roll your own bundling functionality, or leverage some external bundler/minification tool. Those in and of themselves can really come with a learning curve of their own, which can really impede the ramp up speed to working on the application itself.
One of the awesome things that Angular has had for a while now, is that it comes with all of this already setup out of the box! As soon as you use the CLI to fashion a new project, and start coding, you can rest assured that it will package the application up for production in a very efficient way, utilizing the chunking strategy mentioned above. It really is amazing how it all just works! Not only does it provide a way to bundle things, you can also very easily use the router to load these chunks of code lazily as users interact with specifics parts of the application.
Can it help in other ways?
We have talked a lot about why this important — it really reduces the load times of the application if done properly. But in addition to that, I want to focus another added benefit of this behavior. Not only can optimize the performance of your application, but you can also better architect your client code so that only the bits of functionality that the client needs are exposed any given time. This may sound a bit like the same thing, but I am more specifically talking about the idea of “access control”.
I have always believed in the old adage of “never trust the client”. I have worked in various industries but find this to always be true and helpful to keep in mind when developing systems. Distributed systems do indeed make this even more challenging, when you talk about code bases split across a set of microservices, with each potentially having a different tech stack or support/dev team entirely. I always try to tell myself that whatever access control or security applied on the front end is just icing on the cake. If you also stop and think about the fact that essentially all application code is downloaded to the client for whatever use case they wish, you may start to consider the possibility of not giving them code they should not have in the first place. Yes, I get that this code is (hopefully) minified, uglified, obfuscated, etc — but I still pretend or tell my self that all of it is openly exposed to the client in whatever way they wish. This may be antiquated, but I think it just helps me be cognizant of what the client distinctly has access to. Do I want method A to be viewable? Does that user actually have the ability to use that method?
While system architecture today may make this increasingly more difficult, Angular has stepped up in a big way in my opinion to help with this effort. For several releases of Angular, we have been able to seperate administration for example from the non-administration code — with one major gotcha.
Services
Prior to version 6, when you created a service in Angular, it was essentially provided as a singleton in the root/global scope. Basically, as long as the module declared the service as part of its “bundle”, it would be available for any caller on the client. This was done using the providers
array in the module. Furthermore, if you had a primary bundle that declared a service, but then lazy loaded a second module, the components in your second module would still be able to execute or call functions defined in the primary module bundle. At first, this probably seems great — you keep your code pretty dry, and you execute whichever function you need. But what if you want to provide access to granular parts of the application and not others? What if you have different classifications of administrative users, and operations that they can perform? As the code base grows, this becomes more tangled, but harder to segment.
With version 6, if you generate a service, you will notice a new decorator
The new part is so subtle, it can easily be overlooked, but it is the providedIn
part. When you create a new service via the CLI, ng g s angular6-global
, you will get what you see here. This is akin to the old style service declarations before version 6. It simply attaches this services to the root or basically global scope, so that any bit of code can use the functions provided here. Additionally, you no longer need to use the providers
array syntax on the module. Simply create your service, and define how it should be provided directly in the service itself. No longer will you forget which module provided it!
Where the magic comes in is that by changing that providedIn
to a module name, as opposed to a string of ‘root’, the service functions will be confined (and only executable) to that module.
As an example, this service will only be available to components within the module named MyModule
.
When is this a good fit?
The architecture that I leverage this in specifically, is that I like to have a top level administration module, which has its own routing module as well. Within this, I like to setup lazy loaded feature modules for each section of admin needed. These confined services fit into this perfectly to where the client only gets the code it needs for speed, as well as level of access.
But wait, there is more!
This is, of course, only the tip of the iceberg. Tomas Trajan has an excellent detailed write up that further explains this new functionality in full detail. The way this change was added is just perfect — library owners can leverage it keep their code really DRY, while those of us monkeying on applications get to better architecture and decide how the packaged code is distributed to the client.
I am sure you have other use cases for this, or perhaps it’s mind numbingly boring to you. As someone really just getting going on their Angular journey, this is definitely a feature that fits with my development background and ideologies.
I would love to hear your thoughts on this change and what it’s future may be with the new releases of angular down the road!