What is it?
According to Angular Docs, “DI (dependency injection) is a coding pattern in which a class asks for dependencies from external sources rather than creating them itself”.
Why do we need it?
The main use of DI is to avoid hard dependencies between classes. Decoupling the object creation from using these objects enables you to replace dependencies without changing the class that is using them.
In Angular, DI is mostly used to provide services. An example of a commonly used service is HttpClient, which allows you to make backend calls. DI is also used to provide values which can be strings, numbers or even functions.
How do we use it?
To create a service in Angular, we just need to add the @Injectable decorator before the class definition. We can also create our service through angular CLI with the command “ng generate service logger”, or the abbreviated version: “ng g s logger”. This will create a file with the following code:
The @Injectable decorator accepts “providedIn” as an optional parameter to determine which injectors will provide the injectable. It can accept:
- Type<any> – It can be any Angular module (@ngModule) or other InjectorType.
- ‘null’: in this case, the injectable won’t be provided in any scope and it has to be manually added to a providers array of some Angular module, component or directive.
- ‘root’: this is the main injector. When provided in root, it will be available for all eagerly loaded module providers.
- ‘platform’ : this will create a singleton shared by all applications on the page.
- ‘any’ : creates a unique instance (not singleton) in each lazy loaded module.
To inject the service we just need to add it to the constructor. Since we already have added the @Injectable decorator, Angular can recognise our service as a dependency.
How we provide dependencies in angular
To specify this service class as the provider token, we just need to include the service in the providers array, such as:
This ‘providers’ array is part of the annotation of the following decorators: @Component, @Directive, @Module or @Injectable.
When provided this way, the injector will instantiate the class using the ‘new’ operator. It is, in fact, the shorthand of:
We can tell the injector how we want to create the dependency value. To do that, we have four different ways:
- useClass: instantiates a provided class when a dependency is injected
- useExisting – allows us to reference any existing token.
- useFactory : defines a function that constructs a dependency.
- useValue: provides a static value which is used as a dependency.
As we have seen, this is the provider that angular uses behind the scenes when we use the default DI. It also can be used to instantiate the provided class with a different implementation.
This is useful when we have an interface defined with all methods required by the provided class. Most common uses for useClass are:
- Implement a different strategy
- Extend the default class
- Emulate the behaviour in test case
This provider is used to map one token to another. So it doesn’t create a new instance if there is one that already exists.
In this case, the injector will inject the already existing instance of the better logger when the component asks for either LoggerService or BetterLoggerService.
The main difference with useClass is that useClass would end up generating two instances of BetterLoggerService, while using useExisting we would have a singleton.
The useFactory allows you to create a dependency object by calling a factory function.
It is convenient if you don’t know what service provide in advance and only can be known at runtime.
One common case is to create an instance depending on a condition, for example if a feature is enabled. In this example we use a factory provider to create a new instance for loggerService depending on a configService flag.
As we can see in the example, we can use the ‘deps’ array to specify any dependency needed to instantiate the class, in this case the ConfigService.
The useValue key lets you associate a fixed value with a DI token.
It is usually used with non-class elements like strings or config objects. Most common uses are to provide runtime configuration constants.
This technique is mostly used to provide runtime configuration constants such as URL domains and feature flags.