@codecapers/fusion

A simple automated dependency injection library for TypeScript, supporting React class and functional compoents.

Usage no npm install needed!

<script type="module">
  import codecapersFusion from 'https://cdn.skypack.dev/@codecapers/fusion';
</script>

README

Fusion

A simple automated dependency injection library for TypeScript, supporting React class and functional components.

Learn more about Fusion in this blog post:

If you like this project, please star this repo and support my work

Aims

  • To have a simple dependency injection library with minimal configuration that can be used in TypeScript code and with React.

Features

  • Less than 400 lines of code (used to be 300, but you know how it goes, I keep adding extra stuff)
  • Configuration via TypeScript decorators.
  • Injects properties into generic TypeScript class.
  • Injects properties into React class components.
  • Injects parameters into React functional components.
    • Unfortuntely decorators can't be applied to global functions (seems like a big thing missing from TypeScript??) - so the injection approach for functional components doesn't use decorators.
  • Automated dependency injection.
    • Just add mark up and let Fusion do the wiring for you.
  • Detects and breaks circular references (with an error) at any level of nesting.
    • But only when NODE_ENV is not set to "production" (to make it fast in production).
  • Unit tested.

Examples

See the examples sub-directory in this repo for runnable Node.js and React examples.

Read the individual readme files for instructions.

There's also a separate repo with separate examples for React class components and functional components.

Usage

First enable decorators in your tsconfig.json file:

"experimentalDecorators": true

Install the Fusion library:

npm install --save @codecapers/fusion

Import the bits you need:

import { InjectProperty, InjectableClass, InjectableSingleton, injectable } from "@codecapers/fusion";

Create dependencies

Create dependencies that can be injected:

log.ts

//
// Interface to the logging service.
//
interface ILog {
    info(msg: string): void;
}

//
// This is a lazily injected singleton that's constructed when it's injected.
//
@InjectableSingleton("ILog")
class Log implements ILog {
    info(msg: string): void {
        console.log(msg);
    }
}

Note: if you can't get over the magic string, please skip to the last section!

Inject properties into classes

Mark up your class to have dependencies injected:

my-class.ts

import { InjectProperty, InjectableClass } from "@codecapers/fusion";
import { ILog } from "./log";

@InjectableClass()
class MyClass {

    //
    // Injects the logging service into this property.
    //
    @InjectProperty("ILog")
    log!: ILog;

    myFunction() {
        //
        // Use the injected logging service.
        // By the time we get to this code path the logging service 
        // has been automatically constructed and injected.
        //
        this.log.info("Hello world!");
    }

    // ... Other functions and other stuff ...
}

Now instance your injectable class:

import { MyClass } from "./my-class";

// The logging singleton is lazily created at this point.
const myObject = new MyClass(); 

Injected properties are solved during construction and available for use after the consturctor has returned.

So after your class is constructed you can call functions that rely on injected properties:

myObject.myFunction();

Inject parameters into functions

This can be used for injection into React functional components.

Create a functional component that needs dependencies:

my-component.jsx

import React from "react";
import { injectable } from "@codecapers/fusion";

function myComponent(props, context, dependency1, dependency2) {

    // Setup the component, use your dependencies...

    return (
        <div>
            // ... Your JSX goes here ...
        </div>;
    );
}

Wrap your functional component in the injectable higher order component (HOC):

my-component.jsx (extended)

export default injectable(myComponent, ["IDependency1", "IDependency2"]);

The exported component will have the dependencies injected as parameters in the order specified (after props and context of course).

Getting rid of the magic strings

I like to get rid of the magic string by using constants co-located with the dependencies:

log.ts

const ILog_id = "ILog";

//
// Interface to the logging service.
//
interface ILog {
    info(msg: string): void;
}

//
// This is a lazily injected singleton that's constructed when it's injected.
//
@InjectableSingleton(ILog_id)
class Log implements ILog {
    info(msg: string): void {
        console.log(msg);
    }
}

Then use the constant to identify your dependencies:

my-class.ts

@InjectableClass()
class MyClass {

    //
    // Injects the logging service into this property.
    //
    @InjectProperty(ILog_id)
    log!: ILog;

    // ... Other properties and methods ...
}

Have fun! There's more to it than this of course, but getting started is that simple.

See the blog post to learn more.