This Won’t Hurt a Bit — Dependency Injection Tokens in Angular

Maria Korneeva
ngconf
Published in
6 min readApr 29, 2021

--

Dependency injection in Angular

Dependency injection isn’t usually part of Angular 101, yet it is a very powerful feature. If you check out the official glossary, it states that DI tokens are “A lookup token associated with a dependency provider, for use with the dependency injection system.” Everything’s clear? Well, then you can stop reading here.

If you — just like me — have been avoiding this whole dependency stuff for months or years, read on. Using it won’t hurt a bit, after all.

Dependency injection

Let’s say, you have a component that consumes some service. Following the principle of loose coupling, you want to keep your consumer (component) as independent from implementation of external functionality (service) as possible. Generally speaking,

dependency injection is a design pattern and mechanism for creating and delivering some parts of an application (dependencies) to other parts of an application that require them

— in a loosely coupled, flexible and independent way.

Dependency injection token (DI token)

Girl needs some coffee

So how do consumers get the stuff they need? When the app bootstraps, the so-called injector is created. An injector can be thought of as a servant that checks orders of its customers and fulfills their requests. For instance, it can make some coffee (instantiate the CoffeeService) and bring it to the table no. 5. Customers at tables 4 and 8 both want some tea. Our smart injector looks up at their specific wishes and instantiates MintTeaService for the one and CamomileTeaService for the other. Notice that you can configure injectors with different providers that can provide different implementations of the same dependency.

However, how does the injector know, what meal to serve? Our customer (component) must first place its order. It can do so by requesting the meal (dependency) in its constructor.

//coffeeCustomer.tsconstructor(coffee: CoffeeService){};

coffee:CoffeeService is an example of dependency injection token which is a convenient way to inject classes (a.k.a. class dependencies). Now our injector collects all orders from the Angular-app-cafe, a.k.a. it creates a map of any internal dependencies. The DI tokens act as a key to that map. The dependency value is an instance, and the class type serves as a lookup key. Here, the injector uses the CoffeeService type as the token for looking up coffee. This can be compared to the waiter jotting down “1 cap” for “1 cup of cappuccino” in his or her notebook.

For the next step, we need to know, how to make coffee. This is where a recipe (a.k.a. provider) comes into play. A provider object defines how to obtain an injectable dependency associated with a DI token. I must admit that the recipe metaphor is rather oversimplified here. Your cafe might just warm up the meals provided by the franchise (providedIn: ‘root’), cook with regional products only (providedIn: OnlyMyCafeModule), provide individual catering upon component request (providers: [MyCafeService]), etc. If you are interested in this part, read the official documentation on providers.

Let’s continue with our cafe metaphor. Your cafe has some menu or a set of dishes that are usually served:

providers: [CoffeeService, BurgerService, PizzaService]

In a more verbose way, it means the following: to prepare coffee (provide: CoffeeService), use boiled water, coffee, and a cup (useClass: CoffeeService).

[
{ provide: CoffeeService, useClass: CoffeeService },
{ provide: BurgerService, useClass: BurgerService },
{ provide: PizzaService, useClass: PizzaService }
]

The provide property holds the token that serves as the key for both locating a dependency value and configuring the injector (telling our servant what to cook). The second property is a provider definition object, which tells the injector how to create the dependency value (telling our servant how to cook). That is why, if you are running out of milk, you can offer your customer coffee with soy milk instead:

[
{ provide: CoffeeService, useClass: CoffeeWithSoyMilkService }
]

For more details on how to provide different dependencies, check out the official guide on injection providers. For some really deep insights concerning injector hierarchy check out the following article.

In Angular, dependencies are typically services, but they also can be values, such as strings or functions. Let’s say you serve a soup of the day. Every day, when the cafe opens (the app bootstraps), you decide what you gonna offer. It is pretty common that your guests ask what soup is being served as soup of the day. In this case, you can inject the following information:

[
{ provide: SoupOfTheDayName, useValue: 'Onion Soup' }
]

Notice that we use “useValue” instead of “useClass”? This is because our ‘Onion Soup’ has no class. There are times when the dependency we define is either a primitive, object, or function. In such a scenario, the class token cannot be used as there is no class (there are no interfaces in JS, either!). Angular solves this problem by introducing InjectionToken so that you can use it as a DI token in your provider.

Component, injector and provider

By the way, your cafe framework (Angular) has some pre-defined InjectionTokens (e.g. information about your opening hours, etc.). Here is the list:

Summary

Let’s have a look at the whole picture:

You run a cafe where you have a certain set of dishes that you can cook. Whenever a customer orders something, you jot down the order, check if it’s on the menu, look up in the recipe book, cook it and serve it to your customer. You can offer dishes or information (e.g. what is your soup of the day).

Translation into the Angular world:

When an Angular app bootstraps, an injector collects dependencies from all consumers and creates a dependency map. Each injection is configured by a provider that states how the dependency should be instantiated. The injector checks it, instantiates the dependency and serves it to the consumer. You can inject classes or values (e.g. configuration object or some string).

So, as you see, it didn’t hurt a bit!

P.S. In the following stories I’m going to tell you more about specific use cases for injection tokens, e.g. app initialization or configuration.

[Disclaimer: did I miss something / is something not quite correct? Please let me and other readers know AND provide missing/relevant/correct information in your comments — help other readers (and the author) to get it straight! a.k.a. #learningbysharing]

Now that you’ve read this article and learned a thing or two (or ten!), let’s kick things up another notch!
Take your skills to a whole new level by joining us in person for the world’s first MAJOR Angular conference in over 2 years! Not only will You be hearing from some of the industry’s foremost experts in Angular (including the Angular team themselves!), but you’ll also get access to:

  • Expert panels and Q&A sessions with the speakers
  • A friendly Hallway Track where you can network with 1,500 of your fellow Angular developers, sponsors, and speakers alike.
  • Hands-on workshops
  • Games, prizes, live entertainment, and be able to engage with them and a party you’ll never forget

We’ll see you there this August 29th-Sept 2nd, 2022. Online-only tickets are available as well.
https://2022.ng-conf.org/

--

--

Maria Korneeva
ngconf

Learning tech hacks by sharing them with you— that is what drives me. #learningbysharing