Features of Angular DI, about which (almost) nothing is said in the documentation

Angular is a reasonably large framework. It is merely impossible to document and write use cases for each case. And dependency injection…

Features of Angular DI, about which (almost) nothing is said in the documentation

Features of Angular DI, about which almost nothing is said in the documentation

Angular is a reasonably large framework. It is merely impossible to document and write use cases for each case. And dependency injection is no exception. In this article, I will talk about Angular DI’s capabilities that you almost won’t find in the official documentation.

What do you know about the inject function?

The documentation tells us:

Injects a token from the currently active injector. Must be used in the context of a factory function such as one defined for an InjectionToken. Throws an error if not called from such a context.

And then, we see the use of the inject function in the example with the tree shakable token.

That’s all the documentation tells us.

The description of the function is a bit vague, and it is not completely clear in which context inject can be used. Therefore, I conducted a simple experiment and found out that the function works excellent:

  1. In factories of tree shakable providers
  2. In provider factories
  3. In service constructors
  4. In module constructors

Since the factory function InjectionToken cannot have arguments, inject is the only way to get data from the injector. But why do we need this function in services if you can just specify the necessary dependencies directly in the constructor’s parameters?

Let’s look at a small example.

Let’s say we have an abstract Storage class that depends on the Logger class.

Any other class that implements the Storage class must provide it with its dependency manually. If there are a lot of dependencies, this can be very frustrating.

There are two ways out of the situation — pass an injector to the parent class, from which all necessary dependencies will be extracted, or simply use the inject function! This will save the child classes from proxying unnecessary dependencies.

Profit!

Setting the context for the inject function manually

Let’s go back to the source code. We are interested in the private variable _currentInjector, the setCurrentInjector function, and the inject function itself

If you look closely, the work of the inject function becomes quite apparent:

  • the call of the setCurrentInjector function assigns the passed injector to the private variable _currentInjector, returning the previous one.
  • the inject function gets the value from _currentInjector according to the passed token

It’s so simple that we can easily make the inject function work even in components and directives.

It looks completely unused. And using private Angular functions is not a best practice either. Therefore, I do not advise you to do such things.

Injection flags

InjectFlags is analogous to the Optional, Self, SkipSelf, and Host modifiers. Used in inject and Injector.get. The documentation did not fail here, either. It is almost nonexistent.

A person who knows will immediately see bit masks here. The same enum can be presented in a slightly different form.

Let me show you how to use it.

Using one flag

This is how you can get a stream of router events without worrying that the routing module is not connected.

It looks simple. It is also safe, without unexpected falls and unnecessary events.

Combination of flags

A combination of flags can be used to check that a module has been imported once. And they are combined using bitwise OR.

The value of the desired bit is obtained using a bitwise AND:

Tree shakable services and *SansProviders

*SansProviders — shorthand for base interfaces of common providers ValueSansProvider, ExistingSansProvider, StaticClassSansProvider, ConstructorSansProvider, FactorySansProvider, ClassSansProvider

Tree shakable services are a special way of telling the compiler that a service should not be included in an assembly if it is not used anywhere. In this case, the service is not indicated in the module, rather the opposite — the module is indicated in the service.

As we can see from the example, the module is specified in the property providedIn. This works exactly like the following example.

providedIn can also contain special values: root, platform, and any. This is described quite well in the documentation, but it does not say anything at all about the possibility of using factories (I found one small mention in one of the guides). But if we visit the sources, we will see that we can use factories and all existing providing methods — useFactory, useValue, useExisting, etc.

The most useful way, in my opinion, to use a factory in tree shakable services looks like this

angular-ivy-class-singlton - StackBlitz
Starter project for Angular apps that exports to the Angular CLI

The advantages of this service definition:

  1. Accidental use of the service is excluded. To work with it, you need to import the service module.
  2. The module itself does not need to allocate the static methods forRoot and forChild
  3. Guaranteed to create one instance of the service

With other methods of definition, not everything is so obvious. I can imagine that this might help when using external libraries, which also lack typing.

For example:

In this case, using the JqueryInstance token, we will receive a jQuery instance.

For other types of providers, I suggest you come up with use cases yourself. I would be glad if you share them in the comments.

Component interaction

The documentation lists all the existing ways of interaction between components and directives:

  1. Input binding with a setter, ngOnChanges
  2. Child events (outputs)
  3. Template variables
  4. View/parent queries
  5. General service

But not a word has been said about the fact that a child directive/component can quite easily receive a parent instance via DI.

UPD: found the link to the documentation that describes this method.

The example shows that the child directive gets an instance of the parent component, making it easier for them to interact. The directive can directly change the parent component’s value by processing a click on a host element. Any other way would be at least more verbose.

But why is this possible? The answer can be huge and does not fit into the scope of this article. Someday I will write about this in more detail. In the meantime, I will suggest reading to you about the hierarchy of injectors and ElementInjector (in that order).

Summary

As I said, Angular is an extensive framework. And I’m sure there are many more exciting features in its source. I always share my findings on Twitter. For example, you can find Angular tips under the hashtag #AngularTip. And the Russian translation of the most exciting tweets can be found on the #AngularTipRus hashtag! I will be glad if you share your observations and advice with the community and me. Thank you for your attention!