Preact With InversifyJS for Dependency Injection
Integrate dependency injection to your Preact application. Write readable and less code with services directly injected into your Components and Services.
Join the DZone community and get the full member experience.Join For Free
Preact is a lightweight front-end framework that provides the most useful features from the React framework in a very small, ~3KB, package. One thing that is missing from Preact, or ReactJS, is a mechanism to dependency inject (DI) services. InversifyJS provides a lightweight and powerful Inversion of Control (IoC) framework that allows us to inject dependencies to our
Components and services using decorators. Using InversifyJS on a Preact application, we can reduce the boilerplate code needed for instantiating services.
You can download the completed application at this GitHub repo.
Screenshot of the application we will be building to understand how to use DI in a JS application
I will create a simple TODO list app as an example to show how to use and benefit from dependency injection. The example app is mainly composed of the following two services and some TypeScript and HTML markup to use them.
TODOListServicewhich manages items on the to-do list.
StorageServicewhich saves to-do list items to
And some HTML markup. Key points to look out for in the proceeding article:
- How we use the
@injectable()decorator in our service classes to let the application know that the class should be added to the list of providers that can be injected.
- Use of
@inject()on the constructor of the
TODOListServiceservice to inject dependencies without explicitly calling
- How we build a
@lazyInject()decorator to inject services to our Preact
At the end of this article, you will be able to:
- Create a Preact application.
- Use InversifyJS to inject services into your components and services.
Set Up a Preact Project With TypeScript
Create a directory for your project and cd into it:
Install Preact CLI globally. This allows us to use it on the terminal. I recommend using NVM to create a new environment and install it there, but for this tutorial, I will install it globally.
Create a Preact app with TypeScript. Make sure you have NodeJS 12 LTS+ installed on your machine.
webapp directory is the root directory of our project. All the paths mentioned in this article are relative to this folder.
The above command will create a boilerplate Preact web app with TypeScript in the
webapp directory. Open your
tsconfig.json files and uncomment the lines containing
emitDecoratorMetadata. Also, set the
strict flat to
Let's clean up the application so that we are only left with the application entry class,
- Delete all the folders inside
Now, we need to clean up our
appcomponent. The following is a cleaned-up version of
package.json, update the
dev scripts so that we can access our application at
Now if you run
npm run dev and visit
localhost:3001 on a browser, it should display the text
Hello World! on the page.
Set Up InversifyJS for Dependency Injection
First, we need to install the required packages.
Install InversifyJS and inject decorator:
reflect-metadatapackage allows our decorators on classes and properties to emit their metadata.
inversify-inject-decoratorsgive us decorators and a container that manager our injectables.
And the last two dev dependencies are added so that we can inject services into other services by passing them as arguments to their constructors with the
Now that we have everything installed. Let's modify the
src/.babelrc file to support adding decorators to the constructor. Replace the content of
.bablerc with the following.
Then we need to tell Preact to use our
.babelrcfile for building the App. Open the
preact.config.jsand add the following code as the first line of code on the
For all of this to work, we need to add the
relect-metadata package to our project. Add the following line as the first line in your index.js file.
Initialize Our Service Container and Provider
First of all, we need a container to which we can register our services.
Initialize the container. Create a file
di/services.container.ts and add the following content:
Besides initializing the container, we are also overriding the
lazyInjectdecorator here. Our transform plugin for babel has some issues when
@injectis used with properties. To mitigate that we need to override the default
lazyInjectas mentioned on https://github.com/leonardfactory/babel-plugin-transform-typescript-metadata/issues/2#issuecomment-477130801.
Great! now we can create injectable services and inject them anywhere in our application.
Note: I added
getInMemoryStorage so that this Service work with server-side rendering.
We need to register this with our container in
services.container.ts. Add the following line right after the
export const container... line:
This tells the container to bind the name
StorageService string to the
StorageService class in singleton scope. Singleton scope will make sure that there will be only one instance of
StorageService available to the container. This is great since we only need one instance of
StorageService to access the storage.
Register this service with the container the same way you added
Write the TODO List App
Let's update our app.tsx to the following:
Lots of code, but this is pretty self-explanatory. The
HTMLInputElement let the user type TODO items. Clicking on
HTMLButtonElement next to the
<input> will call the function
addAndClearItem call the
addListItem method to store the list item. Once
addListItem stores data, with will dispatch an event to which we listen and render/update the number of items on the list and items on the list.
That is all folks. Now you have an application what inject services to other service and Components. Some can be used to develop any Vue.js or ReactJS application. It is error prune and messy to instantiate classes. As your codebase gets larger, change to a constructor of one service may require you to update multiple files, or worse you might forget to update some files. Which could lead to bugs. Having a centralized place that manages the lifecycle of these services prevent such bugs plus your code will be more concise and readable.
We are using the same DI system to inject dependencies to our PS2PDF online video compress. We inject a similar StorageService that allows us to store uploaded files' metadata and any video compress options, such as target output size, the user chooses. There are stored in a data structure called
UploadedFile. When user request to compress the uploaded files, we get that information from the storage service and send to the remote server to process the file. There is a periodic service that polls for the status and adds the status data to UploadedFile on the storage. When UploadedFile state change, we emit a sort of event that we listen to in our Component that renders the file convert status.
Inversion of Control (IoC) is a very powerful concept. It allows you to delegate work to the container instead of writing code to instantiate classes. Dependency Injection (DI) is a form of IoC where we register our providers with a container and the container takes care of managing the lifecycle of the providers from object creation to destruction. Hope this article helps you to understand the usefulness of DI and improve your code quality.
Opinions expressed by DZone contributors are their own.