ExpressWebJs supports the dependency injection (DI) software design pattern, a technique for achieving Inversion of Control (IoC) between classes and their dependencies.
Dependency injection involves the use of ExpressWebJs service container*, a container that is used to manage class dependencies.*
This topic provides information on dependency injection in ExpressWebJs. The primary documentation on dependency injection is in the Service container in ExpressWebJs.
Overview Of Dependency Injection
A dependency is an object that another object depends on. For example, examine the following EmailServiceImp
class with a SendEmail
method that other classes depend on:
export class EmailServiceImp{
async sendEmail(emailAddress:string,message:string): Promise<void>{
console.log("EmailServiceImp.sendEmail called");
}
}
A class can create an instance of the EmailServiceImp
class to make use of its sendEmail
method.
Let’s try to send an email in our user class after saving a user:
export class User{
private emailService:EmailServiceImp = new EmailServiceImp();
async saveUser(userData:object){
...after saving user;
emailService.sendEmail(userData.email,"Hello world");
}
}
The class creates and directly depends on the EmailServiceImp
class. Code dependencies, such as in the previous example, are problematic and should be avoided for the following reasons:
To replace
EmailServiceImp
with a different implementation, theUser
class must be modified.If
EmailServiceImp
has dependencies, they must also be configured by theUser
class. In a large project with multiple classes depending onEmailServiceImp
, the configuration code becomes scattered across the app.This implementation is difficult to unit test.
Dependency injection solves these problems through:
The use of an abstract or base class to abstract the dependency implementation.
Registration of the dependency in a service container.
Injection of the service into the constructor of the class where it’s used. ExpressWebJs takes on the responsibility of creating an instance of the dependency and disposing of it when it’s no longer needed.
Dependency
abstraction
Let’s create an abstraction for EmailServiceImp
and implement it.
export abstract class EmailService{
abstract sendEmail(emailAddress:string,message:string): void
}
This abstract class also serves as an interface that is implemented by a concrete class, EmailServiceImp
:
export class EmailServiceImp implements IEmailService {
async sendEmail(emailAddress:string,message:string): Promise<void>{
console.log("EmailServiceImp.sendEmail called");
}
}
Service Provider
In ExpressWebJs service provider class, thebindAsSingletonClass
method binds an abstract interface class or name to a concrete class into the container that should only be resolved one time. Once a singleton binding is resolved, the same object instance will be returned on subsequent calls on the abstract interface class or name.
class AppServiceProvicer extends ServiceProvider {
public register() {
this.bindAsSingletonClass(EmailService, EmailServiceImp);
}
}
Dependency injection
We can now inject EmailService
into User
class.
export class User{
constructor(private emailService:EmailService){}
async saveUser(userData:object){
...after saving user;
this.emailService.sendEmail(userData.email,"Hello world");
}
}
By using the DI pattern, User
class:
You won't use the concrete implementation, instead the
EmailService
interface it implements. That makes it easy to change the implementation without modifying theUser
class.You won't create an instance of the
EmailServiceImp
, it's created by the DI container.
Conclusion
In this article, we discussed what is dependency injection, its importance, when, and how to use it in ExpressWebJs.
To learn more about ExpressWebJs, check out the official documentation. Kindly Join the ExpressWebJs community on Discord.
You can follow ExpressWebJs on Twitter @expresswebjs and don’t forget to star ExpressWebJs Project on GitHub