Inversion of Control: Service Locator in Typescript
Tags: typescript, webdev, programming, javascript
Published: 2022-10-24 06:06:03

I've been working with Angular for a while now (over 2 years), and since I moved to another workplace, I've switched (mostly) to Stencil.

Stencil is a very nice compiler for Typescript that generates web components. If I were to place it, it would be a mixture between Angular and React. It uses [decorators](https://stenciljs.com/docs/api) like Angular does (for the compiler to collect metadata and generate the output code based on that), and [jsx/tsx](https://stenciljs.com/docs/templating-jsx) with [lifecycles](https://stenciljs.com/docs/component-lifecycle) just like React. It is a very powerfool tool to generate [design systems](https://stenciljs.com/docs/design-systems).

In this design system that I've been working on, there was the problem of how to store the global state. I wanted to have a similar version to Inversion of Control (since I really liked it in [Angular](https://angular.io/guide/dependency-injection)). The big advantage of using a IoC system is that you can easily mock the services in tests, instead of needing to overwrite the actual imports in jest (overwriting is fine, but on complicated setups where you want different configurations between iterations, it would get messy because if there is any reference to the module before it is mocked, the mock will not work).

So I came up with a simple solution for a global injectable service that did not care about the actual implementation.

---

First step would be to think about what the service that you want to write will do. Based on that, you can declare an interface with all the public methods of the service. Let's consider a simple service that sets and gets user information.

{% embed https://gist.github.com/CalinaCristian/8e1f4ea94fb8aaa9934c55e3564448a6.js %}

Then we would need to declare a `UserServiceInstanceResolver` static class that will receive an instance of a class implementation of this interface, and store it in order to be able to access it.

{% embed https://gist.github.com/CalinaCristian/7440491bbb26806a9b13c6a8e794e527.js %}

Last but not least, we can implement the actual service

{% embed https://gist.github.com/CalinaCristian/d45a0a707bc17824581240f3d5210e18.js %}

And finally, whenever you want to actually instantiate the singleton service, you can just do it as such:

{% embed https://gist.github.com/CalinaCristian/b6b93a06699553f35468afba515eaff1.js %}

And the actual benefit, whenever you want to test any other component that uses this instance, you can easily instantiate a mock of the app on `UserServiceInstanceResolver.Instantiate` method.

{% embed https://gist.github.com/CalinaCristian/2ffcfede981bf775033a8e4e7ff279c3.js %}

This was all that I actually needed (just a root level global instance). But this code can be extended to support as well a per component instance (or hierarchy instance) and get a closer experience to Angular's DI.

---

Thank you for reading my article. If you enjoyed it, please hit that heart button so others can see it.

---

LE:
After seeing some of the comments, I agree that this is not the best pattern to use, and maybe you should use existing solutions from the implementors of typescript such as [tsyringe](https://github.com/microsoft/tsyringe) or even [decoration-ioc](https://github.com/joelday/decoration-ioc) (which is the extraction of implementation of DI in VSCode). One of the reasons I like writing articles when I explore new ideas is that I can get instant feedback and learn new things so that I can improve those solutions, so thank you for that !