Providers in Angular

Learn via video courses

In Angular, 'providers' are key to its Dependency Injection (DI) system, creating and configuring dependencies for applications. Providers, essentially a collection of Classes/Tokens, facilitate object creation and ensure singleton instances. They're injected into constructors, akin to DI container bindings, defining how dependencies are resolved. This article delves into Angular's powerful DI and providers practical applications.

What are Angular Providers?

The Angular Provider is a mapping of tokens that helps Dependency injection in how object creation should happen. The mapping is a collection of an array of providers. Each provider is uniquely identified as a token.

When we inject the provider on the class level constructor, it looks up for providers collection to instantiate dependency token/class. And then, that instance can be utilized inside a Component, Service, Directive, Pipe, etc.

Whenever any token is injected as a parameter in the constructor, for resolving that token, DI refers to the current Module Injector Tree branch. If not found in the current injector tree, it goes to the parent to resolve the dependency. If the dependency is not resolved, it throws a StaticInjector error.

Configuring the Angular Provider

There are different ways to configure a provider. A provider can configure it conditionally. It can be of various types like string, boolean, date, custom type, etc. Even one can use direct const values to assign to the provider.

For eg, We will use the below example for future examples. We have StorageService and there are two implementations of StorageService. Like LocalStorageService and WebSqlStorageService.

Dependencies can be configured on the below levels.

  1. NgModule level providers array.
  2. Component level providers array.

storage.service.ts

local-storage.service.ts

native-storage.service.ts

Provide

provide is an option where you specify a token name. Token name can be Class, string, or InjectionToken.

Syntax

StorageService is a token and we are telling DI to create an instance of the LocalStorageService class dependency whenever StorageService is injected.

Provider

  • Where we provide an implementation of token / Class
  1. Define Provider in @NgModule or @Component decorator metadata.

    my.module.ts

  2. @Injectable decorator metadata {providedIn: 'root'}, defines tree-shakable singleton service on a root level.

    my.service.ts

DI Token

  • Token-based DI instances creation
  • Ask for instance based on token (component, directive, template, selector, etc)
  • Singleton

Type Token

  • Type token can be anything
  • Component, Directive, Template, selector.

Syntax

Usage

String token

  • Token identifier as a string
  • It requires @Inject to get access to the dependency

Define String token

Usage

Injection Token

  • Now, you can define the string token with its type/custom_type
  • We can also call it an improved version of the string token.
  • It provides an object to pass factory for defining token.
  • It also requires @Inject to use it.

Define Injection token

Usage

factory function with Injection token

The Types of Provider

There are different ways to describe providers

Class Provider: useClass

  • In this case, we provide an implementation of the Provider token by passing Class inside a useClass option.

useClass Example

Switching Dependencies

  • By principle, Dependency Injection is focused on loosely coupling.
  • You can easily mock/fake the token provider, with mock implementation for testing purposes.

Value Provider: useValue

  • Sometimes we don't need a class, but just an object.
  • useValue can be utilized for the same use case.
  • pass a plain object to the useValue function, and that will be considered as the evaluated value of dependency.
  • useValue don't accept a function.

UseValue Example

Suppose we need to load some application configuration object, we can simply use useValue to determine the value of token while initializing the angular application.

Factory Provider: useFactory

There are certain use cases where we had to rely on different implementations. For eg. StorageService is a service that helps to persist data for an application. But when it runs on a different platform, we need a separate implementation for the same.

for Browser => requires LocalStorage for NativeApp => requires NativeStorage

This is the perfect use case for useFactory usage. We can use useFactory to conditionally decide which dependency to be instantiated.

UseFactory example

useFactory accepts a function, we did an isMobile check to conditionally instantiate an instance of desired StorageService.

Usage

useFactory Vs useValue

useFactoryuseValue
Helps DI to decide dependency dynamicallyDirectly pass POJO as dependency
always accept a functionalways accept a POJO object
Can have access to other dependencies using deps optionDon't have access to other dependencies during generation

Aliased Provider: useExisting

  • It can be used to alias an existing dependency instance.
  • It does not create an instance.

UseExisting Example

app.module.ts

app.component.ts

output

When a dependency is injected inside an AppComponent constructor, it refers to AppModule where AppComponent has been declared. For evaluation of dependency, it lookups providers array. There is a LocalStorageService that provide a token, with useExisting of StorageService. So what happens is, that it does not create an instance of LocalStorageService, instead, it refers to the StorageService instance. In other words, we can call it LocalStorageService as an alias of StorageService.

Multiple Providers with the Same Token

If you register multiple providers in angular NgModule with the same token, the compiler won't make any complaints about it. It is okay to have multiple registrations for a single token. The latest token, i.e. registered, should win and create the instance of the same.

In the above example when StorageService is injected, it will refer to the last provider registration. Where we would get an instance of NativeStorageService.

Registering the Dependency at Multiple Providers

Provider scope varies based on where we define the dependency.

Provider Scope

  • Root NgModule - Root module providers can be used to module instantiated inside it. The same applies to all eagerly loaded NgModules
  • Lazily Loaded NgModule - This module creates a separate injector tree, so there providers inside lazily loaded modules refer to the current context first.
  • Define providers on the Component, Directive or Pipe level

:bulb: Dependency lifetime, is decided based on where the dependency is defined.

Module Level

  • It can be defined using @Injectable metadata {providedIn: DashboardModule}
  • It instantiates service instances per module in the lazily loaded module
  • Another way to define a provider on the module level is, to mention dependency in DashboardNgModule's providers array.

@Injectable {providedIn: module}

NgModule's provider option

{providedIn: 'root'}

  • DI generates a single instance of service throughout the bootstrapped application context.
  • Even lazily loaded modules will get access to the same instance.

{providedIn: 'any'}

  • This dependency object will create an instance for a module it is being resolved.
  • We can call it a dependency for including lazily loaded modules.

{providedIn: 'platform'}

  • A special singleton platform injector shared by all angular applications loaded on the page.
  • This can be relatively a lot helpful when multiple angular elements wanted to communicate with each other.

Service Injection Techniques

There are various ways to inject a dependency inside a class.

constructor()

  • Declare a parameter on the constructor level with its type

injector.get()

  • Declare a parameter on the constructor level called injector.
  • injector is aware of all the providers belonging to it
  • Call the injector.get method and pass the token to retrieve the dependency instance.

inject()

  • Property level injection
  • wrap the token inside the inject function, and get an instance of the dependency
  • inject function can not be used inside any other function than the constructor

Service as Singleton

Service is singleton in nature. Take an example of the Below module.

Now suppose, ChartComponent inject DashboardService in the constructor parameter, it creates an instance of DashboardService. But next time when ChartComponent injects DashboardService. It goes to DI, DI checks whether the DashboardService instance is already created for this service or not. If yes, then it returns the earlier created instance. This way we had only generated the DashboardService instance once. That's why we call services singleton in Angular.

Conclusion

  • Define the provider as a Singleton Service
  • provider scope can be controlled where and how it is defined.
  • Different ways to configure the Providers in angular
  • Configuring multi-provider with dependency.