What can be put into the Dependency Injection mechanism in Angular?

Almost every Angular developer can find a solution to their problem in Dependency Injection. This was clearly seen in the comments to my last article . People considered various options for working with data from DI, compared their convenience for a particular situation. This is great because such a simple tool gives us so many possibilities.



But several people unsubscribed to me that it is difficult for them to understand DI and its capabilities in Angular. There are not so many materials on the Internet on how to use DI effectively, and for many developers it comes down to working with global services or passing global data from the root of the application to components.



Let's take a deeper look at this mechanism in Angular.









Do you know your addictions?



Sometimes it is not easy to understand how many dependencies your code has.



For example, take a look at this pseudo-class and count how many dependencies it has:



import { API_URL } from '../../../env/api-url';
import { Logger } from '../../services/logger';
 
class PseudoClass {
   request() {
       fetch(API_URL).then(...);
   }
 
   onError(error) {
       const logger = new Logger();
 
       logger.log(document.location, error);
   }
}


Answer
fetch β€” API, , , .



API_URL β€” ( ).



new Logger() β€” , .



document β€” API .





So what's wrong?



For example, such a class is difficult to test because it depends on imported data from other files and specific entities in them.



Another situation: document and fetch will work seamlessly in your browser. But if one day you need to transfer the application to Server Side Rendering, then the necessary global variables may not be in the nodejs environment.



So what is DI and why is it needed?



Dependency Injection manages dependencies within an application. Basically, for us, as for Angular developers, this system is pretty simple. There are two main operations: putting something into the dependency tree or getting something from it.



For a more theoretical perspective on DI, read about the Inversion of Control Principle . You can also watch interesting videos on the topic: a series of videos about IoC and DI from Ilya Klimov in Russian or a short video about IoC in English.



All magic comes from the order in which we supply and take dependencies.



How scopes work in DI:





What can we put in DI?



The first of the DI operations is to put something into it. Actually, for this, Angular allows us to write the providers array in the decorators of our modules, components or directives. Let's see what this array can consist of.



Providing class



Usually every Angular developer knows this. This is when you add a service to your application.



Angular creates an instance of the class when you first request it. And with Angular 6, we can not write classes in the providers array at all, but tell the class itself where in the DI it should go with providedIn :



providers: [
   {
       provide: SomeService,
       useClass: SomeService
   },
   // Angular        :
   SomeService
]




Providing values



Constant values ​​can also be supplied via DI. It can be either a simple string with the URL of your API, or a complex Observable with data.



Providing values ​​is usually implemented in conjunction with an InjectionToken . This object is the key for the DI engine. First, you say: "I want to get this data for this key." And later you come to DI and ask: "Is there something on this key?"



Well, a common case is the forwarding of global data from the root of the application.



Better to see this in action right away, so let's take a look at stackblitz with an example:



Expand example






So, in the example, we got the dependency from DI instead of importing it as a constant from another file directly. And why is it better for us?



  • We can override the token value at any level in the DI tree without changing the components that use it.
  • We can mock the value of the token with the appropriate data when testing.
  • The component is completely isolated and will always work the same regardless of context.




Providing factories



In my opinion, this is the most powerful tool in Angular's dependency injection engine.



You can create a token that will be the result of combining and converting the values ​​of other tokens.



Here is another stackbitz with a detailed example of creating a factory with a stream.



Expand example






You can find many cases when providing a factory saves time or makes the code more readable. Sometimes we inject dependencies into components just to combine them or transform them into a completely different format. In the previous article, I looked at this issue in more detail and showed an alternative approach to solving such situations.



Providing an existing instance



Not a common case, but this option can be a very useful tool.



You can put an entity that has already been created into the token. Works well with forwardRef .



Look at another example with stackblitz with a directive that implements the interface and replaces another token with useExisting. In this example, we want to override the DI-only token value for the child components of the element that the directive is hanging on. Moreover, the directive can be any - the main thing is that it implements the required interface.



Expand example






DI Decorator Tricks



DI decorators allow you to make DI queries more flexible.



If you do not know all four decorators, I advise you to read this article on Medium . The article is in English, but there are very cool and understandable visualizations on the topic.



Not many people also know that DI decorators can be used in the deps array, which prepares the arguments for the providers factory.




providers: [
   {
     provide: SOME_TOKEN,
     /**
      *     ,  
      * [new Decorator(), new Decorator(),..., TOKEN]
      * .
      *
      *      β€˜null’,     
      * OPTIONAL_TOKEN
      */
     deps: [[new Optional(), OPTIONAL_TOKEN]],
     useFactory: someTokenFactory
   }
 ]




Token Factory



The InjectionToken constructor takes two arguments.



The second argument is an object with a token configuration.



A token factory is a function that is called the moment someone requests this token for the first time. In it, you can calculate a certain standard value for the token or even access other DI entities through the inject function.



Take a look at an example of the implementation of the button click stream functionality, but this time at the token factory.



Expand example






Conclusion



DI in Angular is an amazing topic: at first glance, it doesn't have many different levers and tools to learn, but you can write and talk for hours about the possibilities and uses they give us.



I hope this article has given you the foundation on which you can come up with your own solutions to simplify working with data in your applications and libraries.



All Articles