Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing an Angular service that may or may not exist

I'm working on an Angular project at the minute that is designed to very modular - sections of the app can be enabled and disabled for different clients using Webpack. This structure is working nicely for me so far, but one issue I've run into is working out how to handle services that might not always be present.

My current solution is pretty simple - I use $injector.has() to check if the service currently exists, and if so, I use $injector.get() to grab it:

function initialize($injector) {
    if ($injector.has("barcode")) {
        let barcode = $injector.get("barcode");

        // Do stuff with the barcode service
    }
}

This seems to work - however, I can't find much information on the use of this pattern and whether or not it has any potential downsides.

So, my questions are:

  • Are there any caveats to using the injector this way that I should be aware of?
  • Is there a better/more idiomatic way of doing this?
like image 738
Joe Clay Avatar asked Jun 07 '16 15:06

Joe Clay


1 Answers

This is probably the best way to accomplish what you want; however, in doing this, note that you are moving from Dependency Injection (DI) to Service Locator pattern.

The following from the Angular 2 docs is quite relevant (emphasis mine):

We avoid this technique unless we genuinely need it. It encourages a careless grab-bag approach such as we see here. It's difficult to explain, understand, and test. We can't know by inspecting the constructor what this class requires or what it will do. It could acquire services from any ancestor component, not just its own. We're forced to spelunk the implementation to discover what it does.

Framework developers may take this approach when they must acquire services generically and dynamically.

(Aside: It's worth having a read over what's coming for dependency injection in Angular 2 -- Optional dependencies, Factory providers, etc.)

Angular 1's docs on the matter make reference to it running afoul of The Law of Demeter (which is of course more of a guideline than a Law, right?).

In any case, just some additional things to be aware of:

  • You won't be warned (by having errors thrown) of circular dependencies where this is used; in fact, this is often a technique people use to get around these warnings.

  • Since you now have a dependency on $injector in addition to what you inject with it, your unit tests will either need to mock $injector, or use something like module('moduleThatIsUsingInjectorExplicitly', function($provide) { $provide.value('barcode', barCodeMock);}.

  • As mentioned in the quote above, your code will be a little less clear since these 'optional' dependencies are not defined in the usual places.

Plenty of other reading out there on Service Locator pattern, and contrasting it with DI. Interesting enough, but practical implications for us are probably limited, especially as JavaScript's lack of Interface/Abstract and Angular 1's implementation of DI mean there are fewer real points of difference.

like image 50
JcT Avatar answered Oct 30 '22 10:10

JcT