Angular Dependency Providers

Muhammad Danyal
The Startup
Published in
5 min readMay 30, 2020

Dependency Injection (DI) is a design pattern that creates the dependencies of a class and provides those objects to the class when required. Angular being a nice framework provides a built-in dependency injection mechanism that creates and provides runtime version of a dependency value using dependency injectors.

In order for dependency injectors to provide values, we must first configure the injector which tells the injectors that which values to provide. Injectors configuration is done using providers.

To configure the injector, we supply the value to the providers array of the module or component (depending upon the scope of the provided instance, but that’s another topic).

The simplest way to configure an injector using providers is by supplying the class name itself. For example, to provide an instance of a dependency i.e. “MessageService”, we can write

                     providers: [MessageService]

What really happens

Okay, now the fun part begins here. When we provide the value as a class name, what really happens behind the scene is that angular creates a provider configuration object using this provided value. The class-provider syntax is a shorthand expression that expands into a provider configuration object. For example, the above-provided value expands as

  providers: [{ provide: MessageService, useClass: MessageService }]

The provide property of this configuration object holds the value known as Token which is used by the injector to locate the required dependency value.

The second property of this object holds the actual dependency value which is provided by the injector when its dependency is required. This property can be one of the following, depending upon different use cases discussed in this article.

· useClass

· useValue

· useExisting

· useFactory

Use of these configuration properties depend upon different use cases and are used with different type of tokens.

Provider Tokens

The first property of the configuration object holds the provider token. There are three different ways of defining tokens.

· Type Token

· String Tokens

· InjectionToken (Opaque Token before Angular 4.x)

Type Token

Type token means when we use a defined type i.e. class as token as we did in the above example. We usually provide a type as a token which is expanded in configuration object and tell the injector to provide that type dependency.

 providers: [MessageService]

Now there may be cases when we want to use some different class instead of the same class used as a token. This is possible using useClass property. In the following example, the useClass property tells the injector to use DirectMessageService class when MessageService is required somewhere in the application using DI.

   [{ provide: MessageService, useClass: DirectMessageService }]

Sometimes there are cases when you use a class different from token but that class is already used as a provider. For example, look at the following example

Providers: [ ErrorMessageService, { provide: MessageService, useClass: ErrorMessageService}]

In this case, the injector will create two different instances of the ErrorMessageService class. Now to solve this problem we can use the useExisting configuration property, which will use the existing class instance instead of creating a new instance again.

Providers: [ ErrorMessageService, { provide: MessageService, useExisting: ErrorMessageService}]

Sometimes it’s required to provide a ready-made object rather than asking the injector to create it from a class. To inject an object, which you have already created, configure the injector with the useValue option

const messageObj = {
messageType: ‘Direct’,
messageText: ‘this is a direct message’
};
Providers: [{ provide: MessageService, useValue: messageObj }]

TypeScript interfaces are not valid tokens. So if you want to use interface as a token and provide its implementation as provider it’s not possible. One alternative is to use String Token.

String Token

String token allows us to make objects available via DI without introducing an actual type. We can define a string and use it as a valid token.

const FEATURE_ENABLED =true;let featureMessageToken = 'messageFeatureEnabled';...providers: [
{ provide: featureMessageToken, useValue: FEATURE_ENABLED}
]

All we do is, instead of using a type, we use a simple string as a token. We can inject this dependency using the @Inject() decorator likes this:

import { Inject } from '@angular/core’;
class MyComponent {
constructor(@Inject(featureMessageToken) private featureEnabled) {...}}

This is pretty cool right.

If a token is repeated in the injector configuration, the last one wins, and the injector uses the last provider configuration which has the same token values. In the case of string tokens, there are chances when tokens can be the same i.e. some third module can be using the same string token. In this case, the last found will be picked. To solve this issue, we can use the Injection Token.

InjectionToken

The third way of defining token is InjectionToken. It is the same as string token but it generates a unique value every time so there are no same token conflicts.

import { ReflectiveInjector } from '@angular/core’;class DirectMessageService {};
class ErrorMessageService {};
let Message Token = "DMService";
let Message Token = "EMService";
let injector = ReflectiveInjector.resolveAndCreate([
{ provide: MessageToken, useClass: DirectMessageService },
{ provide: MessageToken, useClass: ErrorMessageService },
]);

Factory Providers

Sometimes it is required to create dependency values dynamically, using some runtime values. For example, we want to create a DirectMessageService class object if the user has direct message settings enabled. In this case, we can use the factory provider, which will check the user settings while creating class instance.

let messageFactory = (settings) => {
if(settings.DirectEnabled)
return new DirectMessage();
};

And use this factory using the useFactory configuration property.

{ provide: DirectMessageService, useFactory: messageFactory};

Multi Providers

The provider configuration object has another interesting property multi. Using multi: true tells Angular that the provider is a multi-provider. With multi providers, we can basically provide multiple dependencies for a single token. This can be used using InjectionToken as defined above. If we ask for dependency for that token, what we get is a list of all registered and provided values.

Angular provides a number of built-in injection-token constants that you can use to customize the behavior of various systems. You can use these built-in tokens as hooks into the framework’s bootstrapping and initialization process. A provider object can associate any of these injection tokens with one or more callback functions that take app-specific initialization actions.

· PLATFORM_INITIALIZER

· APP_BOOTSTRAP_LISTENER

· APP_INITIALIZER

This is pretty much all about Angular providers. Angular documentation also provides detailed information about all these configurations.

Happy providing!

--

--