The new dependency injection system in Angular comes with a feature called “Multi Providers” that basically enable us, the consumer of the platform, to hook into certain operations and plug in custom functionality we might need in our application use case. We’re going to discuss what they look like, how they work and how Angular itself takes advantage of them to keep the platform flexible and extendible.

Recap: What is a provider?

If you’ve read our article on Dependency Injection in Angular you can probably skip this section, as you’re familiar with the provider terminology, how they work, and how they relate to actual dependencies being injected. If you haven’t read about providers yet, here’s a quick recap.

A provider is an instruction that describes how an object for a certain token is created.

Quick example: In an Angular component we might have a DataService dependency, which we can ask for like this:

import { DataService } from './data.service' ; @ Component ( ... ) class AppComponent { constructor ( dataService : DataService ) { } }

We import the type of the dependency we’re asking for, and annotate our dependency argument with it in our component’s constructor. Angular knows how to create and inject an object of type DataService , if we configure a provider for it. This can happen either on the application module, that bootstrap our app, or in the component itself (both ways have different implications on the dependency’s life cycle and availability).

@ NgModule ( { ... providers : [ { provide : DataService , useClass : DataService } ] } ) ... @ Component ( { ... providers : [ { provide : DataService , useClass : DataService } ] } ) class AppComponent { }

In fact, there’s a shorthand syntax we can use if the instruction is useClass and the value of it the same as the token, which is the case in this particular provider:

@ NgModule ( { ... providers : [ DataService ] } ) ... @ Component ( { ... providers : [ DataService ] } ) class AppComponent { }

Now, whenever we ask for a dependency of type DataService , Angular knows how to create an object for it.

Understanding Multi Providers

With multi providers, we can basically provide multiple dependencies for a single token. Let’s see what that looks like. The following code manually creates an injector with multi providers:

const SOME_TOKEN : OpaqueToken = new OpaqueToken ( 'SomeToken' ) ; var injector = Injector . create ( [ { provide : SOME_TOKEN , useValue : 'dependency one' , multi : true } , { provide : SOME_TOKEN , useValue : 'dependency two' , multi : true } ] ) ; var dependencies = injector . get ( SOME_TOKEN ) ;

Note: We usually don’t create injectors manually when building Angular applications since the platform takes care of that for us. This is really just for demonstration purposes.

A token can be either a string or a type. We use a string, because we don’t want to create classes to represent a string value in DI. However, to provide better error messages in case something goes wrong, we can create our string token using OpaqueToken . We don’t have to worry about this too much now. The interesting part is where we’re registering our providers using the multi: true option.

Using multi: true tells Angular that the provider is a multi provider. As mentioned earlier, with multi providers, we can provide multiple values for a single token in DI. That’s exactly what we’re doing. We have two providers, both have the same token but they provide different values. If we ask for a dependency for that token, what we get is a list of all registered and provided values.

Okay understood, but why?

Alright, fine. We can provide multiple values for a single token. But why in hell would we do this? Where is this useful? Good question!

Usually, when we register multiple providers with the same token, the last one wins. For example, if we take a look at the following code, only TurboEngine gets injected because it’s provider has been registered at last:

class Engine { } class TurboEngine { } var injector = Injector . create ( [ { provide : Engine , deps : [ ] } , { provide : Engine , useClass : TurboEngine , deps : [ ] } ] ) ; var engine = injector . get ( Engine ) ;

This means, with multi providers we can basically extend the thing that is being injected for a particular token. Angular uses this mechanism to provide pluggable hooks.

One of these hooks for example are validators. When creating a validator, we need to add it to the NG_VALIDATORS multi provider, so Angular picks it up when needed

@ Directive ( { selector : '[customValidator][ngModel]' , providers : [ provide : NG_VALIDATORS , useValue : ( formControl ) => { } , multi : true ] } ) class CustomValidator { }

Multi providers also can’t be mixed with normal providers. This makes sense since we either extend or override a provider for a token.

Other Multi Providers

The Angular platform comes with a couple more multi providers that we can extend with our custom code. At the time of writing these were

NG_VALIDATORS - Interface that can be implemented by classes that can act as validators

- Interface that can be implemented by classes that can act as validators NG_ASYNC_VALIDATORS - Token that can be implemented by classes that can act as async validators

Conclusion