DEV Community

Cover image for Dependency Injection in Angular in simple words
Danila Egorenko
Danila Egorenko

Posted on

Dependency Injection in Angular in simple words

Hello everyone👋 My name is Danila, Angular front-end developer. I started learning Angular not so long ago, so I often come across complex topics that are incomprehensible and need to be analyzed. One of these topics was Dependency Injection. Well, let's figure it out :)

A few words about DI

Dependency injection, or DI, is a design pattern and mechanism for reusing code across different parts of an application.

A simple example is tea with sugar. We have tea, but without sugar it is not as tasty as with it. To get a tasty drink, we take a teaspoon (injector), add sugar (addiction) from the sugar bowl (provider) and put it in a mug.

Services

The most common dependency in Angular is services, but there can be other meanings as well.

A service in Angular is created like this:

@Injectable({ // Decorator that tells DI that this class can be injected
   providedIn: 'root'
})
class Service {}
Enter fullscreen mode Exit fullscreen mode

The provideIn property specifies where exactly to inject. Its available values:

  • root - to the root upon request
  • platform - allows you to use the service in two or more root components (microservices, widgets)
  • any - in each area of implementation (that is, in regular modules, the instance will be common, but in lazy modules, each will have its own. P.S. will be removed in version 17)

Let's imagine that we have 10 services, but we only use 2 on the page. Then, thanks to dependency injection, we will not call 8 unused services. This will reduce the package size and resource consumption.

Injectors need providers

Injectors look for dependencies, and providers provide them.

The easiest way to specify a provider:

providers: [Service] // where, Service is a dependency
Enter fullscreen mode Exit fullscreen mode

This entry can be interpreted as:

providers: [{ provide: Service, useClass: Service }]
// where, provide (token) is the key to search for the dependency,
// and useClass - creates an instance of the class when implemented
Enter fullscreen mode Exit fullscreen mode

And now we implement

Well, DI is sorted out. What's next? Dependency injection uses Injector and its hierarchies. Let's look at his work:

Angular has 2 types of teaspoon injectors: ModuleInjector (created explicitly and used in modules and @Injectable()) and ElementInjector (created implicitly in every DOM element with an empty value, configured in component and directive providers).

When you create an ElementInjector in the directive/component constructor, it appears in the lexical environment and when further requested, Angular will look for it from your current module (for example, a page), going up the hierarchy until it reaches the highest one - @NullInjector(), which always throws an error (unless you use the @Optional() modifier).

Modifiers

When we create a component/directive, we get the dependencies in the class constructor. Thanks to modifiers, you can determine the visibility or way of creating instances:

constructor(@Optional() service: Service) {}
There are 4 types:

  • @Optional() - optional dependency (used when it is not known whether there will be a dependency or not)
  • @Self() - looks for a dependency locally in a module
  • @SkipSelf() - searches for dependency starting from the parent element (scope)
  • @Host() - indicates that if the DI mechanism has reached the host, then the dependency search should be stopped

If necessary, they can be mixed. But not all, you cannot mix @Host() and @Self(), as well as @SkipSelf() and @Self().

Properties use*

There are 4 methods:

  • useClass - as I wrote earlier, creates an instance of a class
  • useExisting - unlike its predecessor, uses an already created instance (useful for singletons, when one instance is used throughout the application)
  • useFactory - uses a function to inject a dependency (convenient for overriding logic, for example, passing a service as function arguments, obtaining information from the service and using it in the constructor)
  • useValue - allows you to use any value as a dependency

What if we implement something other than a class?

For such cases there is InjectionToken. It can be used, for example, to transfer config in an application.

It is created like this:

import { InjectionToken } from '@angular/core';

const drink = new InjectionToken<string>('Drink')
Enter fullscreen mode Exit fullscreen mode

And it is used like this:

providers: [{ provide: drink, useValue: 'Tea' }]
Enter fullscreen mode Exit fullscreen mode

What if you need an array of dependencies?

If an array is required instead of one dependency, then the multi property is used. Then the values will not be overwritten, but the values will be added to the array.

providers: [
   { provide: drink, useValue: 'Tea', multi: true },
   { provide: drink, useValue: 'Water', multi: true },
]
export class drinkComponent implements OnInit {
   constructor(arr: drink) {}

   ngOnInit() {
     console.log(this.arr); // ['Tea', 'Water']
   }
}
Enter fullscreen mode Exit fullscreen mode

I hope this article will help you better understand how dependency injection works :)

And, of course, good luck to everyone in learning such a beast as Angular😉

Top comments (0)