Angular Provider
Software Craftsmanship AngularAngular Provider is like a command to Angular Dependency Injection system regarding how to get an object/instance for a particular dependency.
This helps us to register dependencies, like classes or functions or even values with the Angular Dependency Injection system.
Now you must be wondering what on earth is the Angular Dependency Injection system
Dependency Injection
Let’s first see what is a Dependency.
Dependency is a service or an object that a class needs to perform its function.
Dependency injection, or DI, is a design pattern in which a class requests dependencies from external sources rather than creating them.
Example
We have a CarFactory
in which Car
object is created.
class CarFactory {
car: Car = new Car();
}
And here is our Car
export class Car {
engine: Engine;
constructor() {
this.engine = new Engine();
}
}
export class Engine {}
In the above example, in order to create a Car
, we first need to create an Engine
.
Because we are instantiating the Engine
class inside the constructor of the Car
class with new
keyword, there is a tight coupling between the Car
class and the Engine
class.
And this is a Problem.
Actually, there are many types of Engine
Like a Flat Engine
export class FlatEngine {}
or an Inline Engine
export class InlineEngine {}
The problem here is that if a Car
class requires a specific type of Engine
it can’t have one.
This Car
can only have a generic Engine
because of the tight coupling.
export class Car {
engine: Engine;
constructor() {
this.engine = new Engine();
}
}
This problem can be resolved by making both classes loosely coupled with each other.
For that, first, we will turn our Engine
class into an interface like this
interface class Engine {}
and let our FlatEngine
and InlineEngine
implement it.
export class FlatEngine implements Engine {}
export class InlineEngine implements Engine {}
With that our Car
class will look like this
export class Car {
constructor(private engine: Engine) {}
}
The responsibility of object creation of the Engine
is no more with the Car
class. And the dependency will be directly injected through the constructor of the Car
class.
The Car Factory can now have Car
with different Engine
types
class CarFactory {
flatEngine: Engine = new FlatEngine();
porsche911: Car = new Car(this.flatEngine);
inlineEngine: Engine = new InlineEngine();
bmwM88: Car = new Car(this.inlineEngine);
}
That was just a basic example of dependency injection.
Now let’s get back to Angular Provider.
Configuring the Angular Provider
To Provide an instance of the dependency, we need to register it in the Providers metadata.
providers: [CarService];
The above is an actual shorthand notation for the following syntax:
providers: [{provide: CarService, useClass: CarService}];
As you can see in the above example, the object has 2 properties. They are provide and provider.
1. Provide
The first property is Provide holds the Token or DI Token The Injector uses the token to locate the provider in the Providers array
Type Token
Here the type being injected is used as the token.
For Example, we would like to inject the instance of the CarService
, we will use the CarService
as the token as shown below
providers: [{provide: CarService, useClass: CarService}];
The CarService
is then injected to the component by using the following code.
export class CarComponent {
constructor(private carService: CarService) {}
}
String Token
We can use a string literal to register the dependency. This is useful in scenarios where the dependency is a value or object etc., which is not represented by a class.
{ provide: 'CAR_SERVICE', useClass: CarService },
{ provide: 'API_URL', useValue: 'https://randomcar.com/api' }
It is then injected using the @Inject
in the constructor of the service/component.
constructor(
@Inject('CAR_SERVICE') private carService: CarService,
@Inject('API_URL') private apiURL: string
) {}
Injection Token
Angular provides InjectionToken
class to ensure that the Unique tokens are created.
The Injection Token is created by creating a new instance of the InjectionToken
class.
export const API_URL = new InjectionToken<string>('api.url');
Register the token in the providers array.
providers: [{provide: API_URL, useValue: 'https://randomcar.com/api'}];
It is then injected using the @Inject
in the constructor of the service/component.
constructor(@Inject(API_URL) private apiURL: string) {}
2. Provider
The second property is the Provider definition object. It tells Angular how to create the instance of the dependency. Angular can create the instance of dependency in four different ways.
Class Provider
useClass
is used when you want to provide an instance of the provided class.
It expects us to provide a type. The Injector creates a new instance from the type and injects it. It is like calling the new operator and returning instance. If the type requires any constructor parameters, the injector will resolve that also.
{ provide: CarService, useClass: CarService }
We can also switch dependencies easily. You can provide a mock class for Testing purposes as shown below.
{ provide: CarService, useClass: MockCarService }
Value Provider
UseValue
is used when you want to provide a simple value.
Angular will inject whatever is provided in the useValue as it is.
It is useful in scenarios like, where you want to provide API URL, application-wide configuration, etc
export const APP_CONFIG = Object.freeze({
devUrl: 'https://dev.randomcar.com/api',
IsDevelopmentMode: true
});
And we can use the APP_CONFIG as provided in the providers array.
providers: [{provide: 'API_CONFIG', useValue: APP_CONFIG}];
Factory Provider
useFactory
expects us to provide a function.
It invokes the function and injects the returned value. We can also add optional arguments to the factory function using the deps array. The deps array specifies how to inject the arguments.
We usually use the useFactory when we want to return an object based on a certain condition.
{
provide: CarService,
useFactory: (config: any, loggerService: LoggerService) => return config.IsDevelopmentMode
? new MockCarService(loggerService)
: new CarService(),
deps: [APP_CONFIG, LoggerService]
}
Aliased Provider
The useExisting
provider key lets you map one token to another.
In effect, the first token is an alias for the service associated with the second token, creating two ways to access the same service object.
{ provide: CarService, useExisting: 'MOCK_CAR_SERVICE' },
{ provide: 'MOCK_CAR_SERVICE', useClass: MockCarService },