Skip to main content

Dependency Injection

Dependency injection is a design pattern that allows you to decouple the creation of objects from their usage. In AxiSpark.js, the dependency injection system is used to manage the creation and lifecycle of objects, making it easier to manage dependencies and promote code reusability.

Fundamentals​

Dependency injection is an Inversion of Control (IoC) pattern that allows you to inject dependencies into a class rather than creating them within the class itself. This promotes loose coupling between classes and makes it easier to test and maintain your code. Let's see how dependency injection works in AxiSpark.js.

First, you need to define a class that will be used as a dependency. This class can have its own dependencies, which will also be injected by the DI system.

service.ts
import { Injectable } from '@axisparkjs/di';

@Injectable()
export class Service {
public doSomething() {
console.log('Service is doing something');
}
}

Then, you can define a class that depends on the Service class. The dependency will be injected into the constructor of the dependent class.

component.ts
import { Injectable } from '@axisparkjs/di';

@Injectable()
export class Component {
constructor(private service: Service) {}

public doSomething() {
this.service.doSomething();
}
}

What happens here is that when you create an instance of the Component class, the DI system will automatically create an instance of the Service class and inject it into the Component constructor. This allows you to use the Service class without having to create it manually.

Using the Injector​

You can use the Injector class to create instances of classes and manage their dependencies. The Injector class is responsible for creating instances of classes and resolving their dependencies.

injector.ts
import { Injector, Injectable } from '@axisparkjs/di';

@Injectable()
export class Component {
constructor(private injector: Injector) {}

public injectSomeComponent() {
const serviceInstance = this.injector.get(Service);
serviceInstance.doSomething();
}
}

The Injector class provides a get method that allows you to retrieve an instance of a class. When you call the get method, the DI system will create an instance of the class and inject its dependencies automatically.

AxiSparkCore also provides a get method that allows you to retrieve an instance of a class from the global injector. This is useful when you need to access a dependency from outside of the DI system.

Injection Tokens​

In some cases, you may want to inject a value that is not a class, such as interfaces. In these cases, you can use injection tokens to provide a unique identifier for the value you want to inject. An injection token an instance of the InjectionToken class, which is used to create a unique identifier for a value. You can link an injection token to a value using the @InjectableToken decorator, which allows you to inject the value into a class.

token.ts
import { InjectionToken, InjectableToken, Injectable } from '@axisparkjs/di';

export const MY_TOKEN = new InjectionToken('MY_TOKEN');

@InjectableToken(MY_TOKEN)
@Injectable()
export class MyService {
...
}

Then, you can inject the value associated with the injection token into a class using the @Inject decorator.

component.ts
import { Inject, Injectable } from '@axisparkjs/di';
import { MY_TOKEN } from './token';

export class Component {
constructor(@Inject(MY_TOKEN) private myService: MyService) {}
...
}

Injection Scopes​

AxiSpark.js supports different injection scopes, which determine the lifecycle of the injected dependencies. The available scopes are:

ScopeDescription
SingletonThe dependency is created once and shared across the entire application. This is the default scope.
ScopedA new instance is created for each incoming request.
TransientThe dependency is created each time it is requested. This is useful for stateless services that do not maintain any state between requests.

Usage of Scopes​

When an injectable class is decorated with the @Injectable() decorator, you can specify the scope of the dependency by using the @InjectableScope decorator. The decorator can be set to one of the available scopes.

service.ts
import { InjectableScope, InjectableScopes, Injectable } from '@axisparkjs/di';

@InjectableScope(InjectableScopes.Scoped)
@Injectable()
export class MyService {
...
}

If you do not specify a scope, the default scope is Singleton. This means that the same instance of the dependency will be shared across the entire application.

Be careful, Singleton scope components can not be injected with Scoped or Transient scope components. If you need to inject a Scoped or Transient component into a Singleton component, you can use the Injector class to retrieve the instance of the dependency at runtime. To handle scoped instances, you have to pass the ScopedContainer to the Injector class when retrieving the instance of the dependency. The ScopedContainer is retrieved from the ExecutionContext object, which is available in all the entrypoints of the application using @Context. For more information about the ExecutionContext object, please refer to the Engine System section of the documentation.