Dependency Injection, in general

An example to understand dependency injection in general.

public engine: Engine;
public tires: Tires;  constructor() {
    this.engine = new Engine();
    this.tires = new Tires();
  }

Angular’s DI

What is DI and why do we need it?

 

Dependency Injection mechanism manages dependencies in your application. For you as an Angular developer, it is a straightforward instrument.

 

There are two operations: you can provide something into your DI tree or take something from it.

What can we provide?

Providing a class

 

It is a case when you provide a service.

Angular creates a class instance when you ask for this the first time. After Angular 6 we can also use providedIn syntax to describe how injectible is provided.

Providing a value

 

You can also provide a constant value. It can be just a string with your API url or a significant observable with data.

 

Providing value is normally used with InjectionToken.

This object is like a key for DI mechanism.

You say "I want to get this data with this key" and ask DI in a component "Do you have something for this key?"

The common case is providing global data from the root of your app.

So, now we get a dependency from DI instead of importing it as a constant from another file directly.

And why is it better for us?

 

  • We can replace the token value at any level of DI tree without any changes in a component

     
  • We can mock a token value providing suitable data in tests

     
  • The component class is fully isolated and can be used without any context

Providing a factory

 

This is likely the most powerful DI feature in Angular.

You can provide some value combining and transforming data from other tokens.

There are many cases when providing a factory saves your time or makes your code more readable.

Sometimes we inject dependencies into components only to combine them or transform their data to another format.

 

For example, you can use a factory to extract some data from ActivatedRoute instead of doing many actions in a component and its children.

Providing an existing one

 

It is not a frequent case but it can be a useful instrument.

You can provide instances that were already created. It can work together with forwardRef.

Take a look at the following sample with Directive implementing an interface and providing instance with useExisting.

we provide a directive with useExisting because we want to reassign a token value in the DI score for children. This is helpful when you have a set of directives/components implementing a common interface.

If you want to inject a particular directive you can also add

private readonly someDirective: SomeDirective in a constructor of your Angular class.

How to avoid Angular injectable instances duplication

Intro

If you want to create only one instance of any service for a whole application, you want to create a Singleton.
 

From wikipedia:

In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one “single” instance.


 

Why you may want it?
 

The most common reason is to share some valuable state between all parts of your application.

But sometimes Angular can create more than one instance of SettingsService, so your settings will vary by instance and it will lead to serious configuration problems in your application.

Well, let’s see why this is happening and how to handle it.

The problem

 

Angular will create new instances for any of InjectionToken or Injectable in cases of using:

 

 

This is happening because Angular creates a new module Injector for any lazy loaded module, this behavior is perfectly described in docs and this article.

Here is the demo with the problem demonstration.

The solutions

 

The most important thing to understand here — adding any Injectable (or InjectionToken) to the @NgModule.providers list for any Eager and Lazy module pair will duplicate such Injectable!

 

So, first step is not to add services that should be singletons to @NgModule.providers list of any module.

 

Basically you can add service to the Application module providers and it will work. But other developers may not know that you want to use this service as a singleton, and somebody will add this service to the providers list of lazy loaded module and Angular will create second instance of it.

You should choose which strategy to use, because there are two with its’ pros and cons:

 

  • static forRoot() @NgModule method
     
  • @Injectable({ providedIn: ‘root’ })

forRoot()

 

forRoot method is a kind of agreement/convention between Angular developers to call this method in the root module only (AppModule for example), so any service will be provided only once.

For this technique you should create module and implement static forRoot(): ModuleWithProviders method.

Note: forRoot method is not handled by any code in Angular Compiler so you can name it as you want (forMySuperHotRootAppModule()?), but it is not recommended.

Good example of forRoot usage it is RouterModule.

 

Here is the demo with forRoot solution.

providedIn: ‘root’

 

When you mark Injectable as provided in root, Angular resolver will know that such Injectable, used in lazy module, was added to the root module, and will look for it in the root injector, not newly created lazy loaded module injector (default behavior).

Huge plus of this solution — angular’s ability to use tree shaking with providedIn.

 

Also with providedIn your tests will not fail, because if all of your services provided in root (99,99% should be provided in root I bet) TestBed will resolve it correctly.

 

Here is the demo with providedIn solution.

The singleton guard

 

You can easily get to know if somebody created the second instance of your service.

 

Also it can be done as a base class:

What if I use forRoot in combination with providedIn: 'root'?

 

There is no differences between using: forRoot or providedIn or forRoot+providedIn. Service will be created only once.

 

What if I use forRoot and providers list?

 

Service will be duplicated.

 

What if I use providedIn and providers list?

 

Service will be duplicated.

Conclusion


Creating multiple instances of the same services can become a problem and Angular provides some abilities to handle it.

Improved Dependency Injection with the new providedIn scopes

'any' and 'platform'

tl;dr

 

'platform' and 'any' are two new ways to define where a service should be resolved.

  • 'platform' makes a service available between multiple apps or Angular Elements
  • 'any' creates isolated (not singleton) services for every child injector.

Official Angular 9 documentation

 

The offical documentation describes the new scopes as follows:

  • 'root' injector, which will be the application-level injector in most apps.
  • 'platform' injector, which would be the special singleton platform injector shared by all applications on the page.
  • 'any' injector, which would be the injector which receives the resolution. (Note this only works on NgModule Injectors and not on Element Injector)

Quick Angular DI recap

 

** Module Injectors **

 

There are two injector hierarchies in Angular:

  • ModuleInjector
  • ElementInjector (NodeInjector in Ivy)

See Hierarchical injectors

In this article we only look at the ModuleInjectors.

 

Your services are in one of those injectors (Most likely in the root injector).

  • Platform Injector (special things like DomSanitizer)
  • root Injector (main injector and place for all eagery loaded module providers)
  • LazyModules Injectors (all lazy loaded modules create a child injector)

** Pre Angular 6 **

 

The only way to define providers was with the providers array. Services were singletons in eagerly loaded modules but could be instatiated multiple times with lazy loaded modules.

** Angular 6+ **

 

Angular 6 introduced tree-shakable providers with providedIn: 'root'.

This has 3 big advantages:

  • Services are tree-shakable
  • Services are singleton for the whole application, also for lazy loaded modules
  • No need to explicitly register the service with a NgModule

If you want to know more about this, read the excellent article by Tomas.

** Angular 9 ✨ **

 

Now with Angular 9 we have two new ways to define providedIn scopes:

  • any
  • platform


Let's have a look at them.

ProvidedIn: root

 

Every service defined with 'root' will be provided in the root injector and is a singleton for the whole application. Lazy modules will use the instance from root.

 

providedIn: 'root' will still be the default choice for most services.

ProvidedIn: platform

 

Every service defined with 'platform' will be provided in the platform injector and is a singleton for all applications. Lazy modules will use the instance from platform.

 

The difference between 'root' and 'platform' is only noticeable when running multiple Angular application in the same window. Both make sure that only one singleton exists even for lazy loaded modules. But when running two applications in the same window, each application has it's own root injector but both share the platform injector.

 

This means that the best use case for providedIn: 'platform' is for sharing services over application boundaries. E.g. with Angular Elements.

 

Use providedIn: 'platform' for sharing services over application boundaries. E.g. with Angular Elements.

ProvidedIn: any

 

Every service defined with 'any' will be provided in every module it is used. That means there might be multiple instances of the same service. That means that every lazy loaded module has it's own instance of the service. All eagerly loaded modules share one instance provided by the root module injector.

 

In the following example is the service used twice. Once within an eagerly loaded module (provided by root) and once in Lazy Module B (provided by its child injector).

 

What's really cool with that approach is, that you can make sure that a child injector always has it's own instance without the need to register the service in the providers list. 💪

Summary

 

'Root' will still be the default for most services. It makes it very convenient to create tree-shakable services which are singleton within an application.

 

'Platform' is most likely used for creating shared services for Angular Elements. If you know another use case, please let me know an create a PR on this article.

 

'Any' is very helpful to make sure a service is a singleton within module boundaries. It's a robust alternative to 'root' to make sure the individual modules don't have a side effect on each other.

Dependency Injection in Angular

By Igor Tikhonov

Dependency Injection in Angular

  • 184