Providers In Angular

Providers In Angular

In this article, We will learn about Angular Providers using examples. The Angular Dependency Injection system can register classes, functions, or values (dependencies) with Angular Providers. The token is used to register the Providers. To find the supplier, the tokens are used. We can make three different types of tokens. Tokens for type, string, and injection. The Provider additionally instructs the Angular Injector on how to construct the dependent instance. You can create the dependency in four different ways: Class Provider (useClass), Value Provider (useValue), Factory Provider (useFactory), and Aliased Class Provider are the four types of providers ( useExisting).

What are Angular Providers

The Angular Provider is a command (or recipe) that explains how to construct an object for a specific token. An array of such commands can be found in the Angular Providers (Provider). A token (or DI Token) in the Providers Array uniquely identifies each provider.

In the Providers metadata, we record the services that participate in dependency injections. We can accomplish this in two ways.

  • Register directly in the @NgModule or @Component's Providers array, or in @Directive.
  • Alternatively, use the @Injectable decorator's providedIn property.

For each component/directive it develops, Angular creates an Injector. It also builds an injector at the root level with app-level scope. For Lazy Loaded Modules, it also constructs a Module level Injector.

The Providers are duplicated for each Injector. We can have many providers with the same dependence. The scope of the dependency is determined by where and how the dependency is registered.

In their constructor, Angular Components and Angular Services define the dependencies they require. Using the Token, the Injector reads the dependencies and looks for the provider in the providers array. It then creates the dependency according to the provider's instructions. The dependency's instance is then injected into the Components/Services by the Injector.

Configuring the Angular Provider

To provide a dependent instance, we must first register it in the Providers metadata.

We registered our ProductService using the Providers arrays in the @NgModule in our last Angular Dependency Injection article.

providers: [ProductService]

The above is a shorthand notation for the following that syntax.
providers :[{ provide: ProductService, useClass: ProductService }]

There are two properties to the above syntax.

Provide

The Token or DI Token is held by the first property, Provide. The Injector looks up the provider in the Providers array using the token. A type, a string, or an instance of InjectionToken can be used as the Token.

Provider

The Provider definition object is the second property. It instructs Angular on how to generate the dependency instance. The dependency instance can be created in four distinct ways by Angular. It has the ability to construct a reliance on an existing service class (useClass). It has the ability to inject a value, array, or object (useValue). It can make use of a factory function, which returns a service class or value instance (useFactory). It can retrieve an instance from a previously created token (useExisting).

DI Token

In the Providers array, the Injector keeps an internal collection of token-providers. The token serves as a key to that collection, and the Injector uses it to find the Provider.

A type, a string, or an instance of InjectionToken can be used as the DI Token.

Type Token

The token is the type that is being injected in this case.

For example, if we want to inject a ProductService instance, we'll use the ProducService token, as seen below.
providers :[{ provide: ProductService, useClass: ProductService }]

Using the following code, the ProductService is then injected into the component.
class ProductComponent {
  constructor(private productService : ProductService ) {}
}

String token

To register the dependency, we can use a string literal instead of a type. This is useful in situations where the dependency is a value, object, or other type of object that isn't represented by a class.

Example

{provide:'PRODUCT_SERVICE', useClass: ProductService }, 
{provide:'USE_FAKE', useValue: true },   
{provide:'APIURL', useValue: 'http://SomeEndPoint.com/api' },    

The @Inject method can then be used to inject the dependency.

Injection Token

The issue with string tokens is that they can be used by two developers in different parts of the program. You also have no control over third-party modules that might use the same token as you. If the token is reused, the last person to register it overwrites any tokens that have already been registered.

InjectionToken is a class provided by Angular to ensure that unique tokens are produced. A new instance of the InjectionToken class is used to produce the Injection Token.
export const API_URL= new InjectionToken<string>(''); 

In the providers array, add the token.
providers: [ 
    { provide: API_URL, useValue: 'http://SomeEndPoint.com/api' }
]

It is then injected into the service/constructor component's using @Inject.
constructor(@Inject(API_URL) private apiURL: string) { 
}

The Types of Provider 

There are various types of providers in Angular Dependency Injection.
  • Class Provider : useClass
  • Value Provider: useValue
  • Factory Provider: useFactory
  • Aliased Class Provider: useExisting


Class Provider: useClass

When you want to supply an instance of the provided class, use the Class Provider useClass.

The useClass method requires that we give a type. The Injector generates and injects a new instance of the type. It's the same as calling the new operator and getting the instance back. The injector will additionally resolve any constructor parameters if the type requires them.

UseClass Example

providers :[{ provide: ProductService, useClass: ProductService }]

Example ProductService is the Token (or key) in the preceding example, and it translates to the ProductService Class. Both the Class name and the token name match in this example.

When both the token and the class name match, Angular provides a shortcut, as shown below.
providers: [ProductService]

Switching Dependencies

As seen below, you can offer a mock/fake class for testing purposes.
providers :[{ provide: ProductService, useClass: fakeProductService }]

The preceding example demonstrates how simple it is to switch dependencies.

Value Provider: useValue

When you need to offer a basic value, use the Value Provider useValue.

Angular will inject whatever is specified in the useValue.

It's beneficial in situations where you need to supply an API URL or an application-wide configuration option, for example.

UseValue Example

We use the token USE FAKE to pass a boolean value in the example below.
providers :[ {provide:'USE_FAKE', useValue: true}]

Using the @Inject method, we can inject it into the AppComponent.
export class AppComponent {
  constructor(
    @Inject('USE_FAKE') public useFake: string
  ) {}

You have the ability to pass an object. Use Object.freeze to lock the configuration's value so that no one can change it.
const APP_CONFIG =  Object.freeze({
  serviceURL: 'www.serviceUrl.comapi',
  IsDevleomentMode: true
});

Register it.
providers: [
  { provide: 'APP_CONFIG', useValue: APP_CONFIG }
]

Inject it as directed below.
export class AppComponent {  
constructor(
    @Inject('APP_CONFIG') public appConfig: any
  ) {}
}

You can also add a feature.
providers: [
  {
    provide: 'FUNC',
    useValue: () => {
      return 'hello';
    }
  }
]

The function will be injected exactly as it is. To receive a value from the function someFunc(), you must call it.
export class AppComponent {
  constructor(
    @Inject('FUNC') public someFunc: any
  ) {
    console.log(someFunc());
  }
}

Factory Provider: useFactory

The Factory Supplier makes advantage of Factory expects us to perform a task. It calls the function and then injects the result. The deps array can also be used to add optional arguments to the factory method. The deps array indicates how the arguments should be injected.

When we wish to return an object depending on a condition, we normally use the useFactory method.

UseFactory example

Consider the scenario in which we wish to inject either ProductService or FakeProductService depending on the USE FAKE setting. Furthermore, one of the services (ProductService) necessitates the use of another service (LoggerService). As a result, we must include USE FAKE and LoggerService in our factory function.
providers: [
  { provide: LoggerService, useClass: LoggerService },

  { provide: 'USE_FAKE', useValue: true },

  {
    provide: ProductService,
    useFactory: (USE_FAKE, LoggerService) =>
      USE_FAKE ? new FakeProductService() : new ProductService(LoggerService),
    deps: ['USE_FAKE', LoggerService]
  }
]

All of the dependencies of the must be passed as a parameter to the factory function. The injector resolves and injects dependencies using the deps array (third argument).
useFactory: (USE_FAKE, LoggerService)

Depending on the value of USE FAKE, we return FakeProductService or ProductService inside the factory method.
=>
   USE_FAKE ? new FakeProductService() : new ProductService(LoggerService)

In the final option, we must instruct the Injector on how to inject the Factory function's dependencies. It's worth noting that the order must match that of the factory function argument.
deps: ['USE_FAKE', LoggerService]

The above example code as follows.
export function resolveProductService(USE_FAKE, LoggerService) {
  return USE_FAKE
    ? new FakeProductService()
    : new ProductService(LoggerService);
}
 
 
 
 
  providers: [
    { provide: LoggerService, useClass: LoggerService },
 
    { provide: 'USE_FAKE', useValue: false },
 
    {
      provide: ProductService,
      useFactory: resolveProductService,
      deps: ['USE_FAKE', LoggerService]
    }
  ]

useFactory Vs useValue

We used the following code in the useValue example.
providers: [
  {
    provide: 'FUNC',
    useValue: () => {
      return 'hello';
    }
  }
]

The useValue method returns the function in its original state. To acquire the value, you must invoke the function (someFunc()).
export class AppComponent {
  constructor(
    @Inject('FUNC') public someFunc: any
  ) {
    console.log(someFunc());
  }
}

You can achieve the same result by using the useFactory method.
providers: [
    {
      provide: 'FUNC',
      useFactory: () => {
        return 'hello';
      }
    }
  ]

The application of The factory function is called and the result is returned. As a result, you'll get the function's value rather than the function itself in the component.
export class AppComponent {
  constructor(
    @Inject('FUNC') public someFunc: any
  ) {
    console.log(someFunc);
  }
}

Aliased Provider: useExisting

Aliased Provider is a useful tool. When you wish to replace an existing provider with a new one, useExisting.

UseExisting Example

providers: [
  { provide: ProductService, useExisting: NewProductService },
  { provide: NewProductService, useClass: NewProductService },

In the preceding example, we use useExisting Provider to map the ProductService token to the NewProductService token. When we use the ProductService, it will return the NewProductService.

Also, the token must be passed to the useExisting method, not the type. UseExisting with string tokens is demonstrated in the following example.
providers: [
  { provide: ProductService, useExisting: 'PRODUCT_SERVICE' },
  { provide: 'PRODUCT_SERVICE', useClass: NewProductService },

Multiple Providers with the same token

You can fill the Providers array with as many dependencies as you want.

If you add more than one provider with the same token, the Injector does not complain.

NgModule, for example, uses the same token ProductService to add both ProductService and FakeProductService.
@NgModule({
 
  ... 
  providers: [
    { provide: ProductService, useClass: ProductService },
    { provide: ProductService, useClass: FakeProductService },
  
  ]
})
export class AppModule {}

In this case, the last person to register wins. Because we register FakeProductService last, the ProductService token always injects FakeProductService.

Registering the Dependency at Multiple Providers

You can also use Multiple Providers to register a Dependency.

For example, in this case, we use NgModule to register the ProductService.
@NgModule({
 
  ... 
  providers: [
    { provide: ProductService, useClass: ProductService },  
  ]
})
export class AppModule {}

We can also use AppComponent to register it. AppComponent always obtains the dependency registered in the component itself in this situation.
@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  providers: [ProductService]
})
export class AppComponent {
  products: Product[];

Provider Scope

The service will be available everywhere in the application if it is provided in the @ngModule of the root module or any eagerly loaded module.

If we give services in the @Component, @pipe, or @Directive, they are only available in that component and its children.

The @ngModule of the lazy loaded module provides services that are only available in that module.

Singleton services

Each Injector creates a singleton object for each dependency that the provider has registered.

Consider the case of a service configured in @ngModule. When Component A requests the service, a new instance of the service is created. When Component B requests the same service, the injector does not construct a new instance of the service; instead, it reuses the one that was previously generated.

However, if the service is registered in both @ngModule and Component A. A fresh instance of the service is always created for Component A. While the instance of the service registered in @ngModule is sent to other components.

Conclusion

We learned how to use Angular Providers to register dependencies. The Angular injector will be covered in the following article.

I hope this article helps you and you will like it.👍

If you have any doubt or confusion then free to ask in comment section.

Post a Comment

Previous Post Next Post