DEV Community

Cover image for Inversion of Control: Service Locator in Typescript

Inversion of Control: Service Locator in Typescript

Cristian-Florin Calina on October 24, 2022

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 ...
Collapse
 
mariocalin profile image
Mario • Edited

This is more of a Service locator pattern than dependency injection pattern. You are not "injecting" anything really.

Collapse
 
cristianflorincalina profile image
Cristian-Florin Calina

Correct ! I will edit it, since you are right

Collapse
 
kralphs profile image
Kevin Ralphs

Just use TSyringe. You'll have a full IoC container that will feel like Angular's DI system. That will allow you to ditch having IUserServiceModel as a class and leave it an interface by using the Inject decorator.

If you were to stick with this method, I recommend marking IUserServiceModel as an abstract class with abstract methods that you want to force subclasses to define.

And maybe it's something Stencil needs or you just trying to make the code feel fuller, but it looks like you're writing Java style code in Typescript.

Collapse
 
cristianflorincalina profile image
Cristian-Florin Calina • Edited

actually that's my bad in the gist that I put it as a class, I corrected it now. It should be an interface. I'll try out TSyringe (but idk how well it works with stencil which has it's own typescript compiler) since I do like DI better than a Service Locator, thank you for the suggestion !

Collapse
 
click2install profile image
click2install

I would just use a container. Newing up a concrete to pass to a resolver to maintain a singleton reference is a lot of indirection for no real benefit over a simpler singleton or container resolution, or both. At least your resolver could be generic but passing a concrete to it could be managed a lot cleaner and simpler.

Collapse
 
cristianflorincalina profile image
Cristian-Florin Calina • Edited

The benefit is the abstractization from implementation. You don't care about the service, you care about the interface being respected (in the components that need the implementation, you import the locator which doesn't have any actual imports to the implementation, just the interface, so no need to mock the import in tests). Can you give me an example of how to simplify the singleton ?

By container resolution you mean the TSyringe container?

Collapse
 
click2install profile image
click2install • Edited

Yes, a DI container like TSyringe or one of the many others. A Service Locator in most situations is considerd an anti-pattern. You're effectively newing up a service so you can have a resolver (which should be generic) maintain a singleton instance. A container will do that for you without having to new anything up.

Similarly, if you implememt a singleton you can just use that and mock the interface. A simpler singleton would maintain it's own single instance an not have to be newed up to pass to a resolver (that you wouldn't need) which returns that instance.

The abstraction you mention is primarily coming from your resolver which isn't required. You already have an interface against your service.

Thread Thread
 
cristianflorincalina profile image
Cristian-Florin Calina • Edited

ok, maybe I don't fully understand, but having the singleton maintain it's own instance wouldn't mean it's imported wherever it is used? So you would have the actual implementation coming up in the import? That means you would be needing to mock the instance on it, but you still need to mock a file import, right?

But I see the point of a container, I will update the conclusion to specify that there are libraries that can do that better such as tsyringe

Thread Thread
 
click2install profile image
click2install

That's correct the singleton instance is cached by the module. If you use a typical singleton implementation, which you generally want to avoid, you would mock the import against the interface - which is technically no different to what you're doing now. You're currently newing up either a concrete or a mock and having your resolver return that instance. Your resolver is a minimally specific container-like structure that is resolving what you pass it. The benefit of using a DI container, as the peferred approach, is its more generic, adheres SOLID and you can specify lifetime.

Collapse
 
stradivario profile image
Kristiqn Tachev

Definetely you should give it a try with @rhtml/di

I tried to make the smallest dependency injection library written in typescript.

Transpiled it is less than 3 kb

github.com/r-html/rhtml/tree/maste...

Great jkb for the article :)

Collapse
 
nausaf profile image
nausaf • Edited

Hi Cristian,

Service Locator, as opposed to Dependency injection, is considered to be an antipattern by many.

My opinion is that it is conceptually easy to understand because we think we can just replace a call to constructor with a call to service locator in the body of our code but still retain the ability to change the implementation (in config, at app startup or perhaps even dynamically, or in tests which seems to be your motivation).

What tends to happen in practice is that if a class/function B is sufficiently decoupled from a piece of code A that it is resolved from an IoC container, it will probabaly also need to be mocked out in unit tests of A. This is where service locator leads to problems:

  1. You have to look into the guts of A to see what will need to be mocked out instead of it being advertised in A's constructor if using Dependency Injection.

    This goes contrary to the idea of test-driven development and that fact that we typically write (or perhaps should write) tests that treat the unit-under-tests as a black box with only its interface visible (include JSDoc comments) and not its implementation. This decoupling of test from internals of code-under-test leads to both ease of refactoring and less brittle tests.

  2. It is easier, and probabaly faster in terms of test execution time, to just create mocks and stubs and then inject them into a unit-under-tests (as you would with Dependency Injection) than to instantiate a full IoC container, put the mocks there, and make sure it's available to the unit-under tests (which is what Service Locator forces you to do).

This is my view anyway. Thanks for the post though. There's some cool stuff there and I really want to look at Stencil for generating design systems.

Collapse
 
cristianflorincalina profile image
Cristian-Florin Calina • Edited

I agree with 1. but on 2. I don't see how it's more difficult to use the service locator and add the mocked instances to it. I think it takes as many lines of code as it does to set the instance to the container for DI. I don't know how the speed is measured as well, it's an assumption that it is faster, if you have any performance measurements between the approaches I'd like to see them.

I added to my article as well that DI is a better approach than this, this is just simpler to implement and easier to understand and get you into IoC (since I assume that most JS FE devs don't know much about IoC solutions -- except for Angular). Writing this article helped me learn a lot as well from all the comments that I've received so thank you as well for contributing to that !

Collapse
 
ecyrbe profile image
ecyrbe

Where is inversion of control ? This is the opposite, you are using singleton pattern.

Collapse
 
cristianflorincalina profile image
Cristian-Florin Calina

It's more like a Service locator pattern, but yea, using singleton

Collapse
 
itechthemeg profile image
itechthemeg

Right