npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

ts-simple-dependency-injector

v1.2.0

Published

Simple TypeScript Dependency Injection Usable with React Components

Downloads

10

Readme

ts-simple-dependency-injector

Simple TypeScript Dependency Injection Usable with React Components.

Constructor Injection Mechanism

The mechanism of injecting dependencies into the constructor takes advantage of TS/ES6 default parameter assignment. No annotations, no metadata, and no reflection. We just let the engine do all the work, and it does a really good job.

import { resolve } from "../MyDependencyHelper.ts";

class Class1 {
    constructor(
        public c2 = resolve(Class2),
        public c3 = resolve(Class3),
    ) { }
}

class Class2 {
    constructor(
        public c4 = resolve(Class4),
    ) { }
}

class Class3 {
    constructor(
        public c5 = resolve(Class5),
    ) { }
}

class Class4 { } // Terminal node.
class Class5 { } // Terminal node.

Container Creation & Type Registration

The container is capable of registering types that exist during runtime (abstract class, class) to their corresponding implementations:

import { Container } from "ts-simple-dependency-injector";

abstract class PureAbstractClass {
    public abstract get C1(): Class1;
}

// Pure abstract classes can be implemented like interfaces in TS.
class PureAbstractClassImplementation implements PureAbstractClass {
    constructor(private c1 = resolve(Class1)) {}
    public get C1() {
        return this.c1;
    }
}

// Container creation
const container = new Container();
const resolve = container.getResolver(); // Resolve function bound to our container to be passed around.
                                         // You can export this function from your dependency helper.

// Dependency setup

// Bottom-up composition tree construction.
container.register(Class4, Class4, "permanent");
container.register(Class5, Class5, "permanent");

// Class2 already resolves Class4 during its construction here, so Class4 must be registered at this point.
// Circular dependencies are not possible (which is good from the architectural point of view).
container.register(Class2, Class2, "permanent");
container.register(Class3, Class3, "permanent");
container.register(Class1, Class1, "permanent");

// Composition root
container.register(PureAbstractClass, PureAbstractClassImplementation, "permanent");

Implicitly-typed Resolving

The container is capable of implicitly-typed resolving.

const impl = resolve(PureAbstractClass);
// "Impl" will contain fully initialized PureAbstractClassImplementation will all its dependencies
// and it will be of type PureAbstractClass. No need to declare the type explicitly.

React Dependency Injection

The injector makes it very simple to inject dependencies (such as stores) to React components.

export class MyComponent extends React.Component<Props, State> {
    constructor(
        props: any,
        context: any,
        private myStore = resolve(IMyStore),
        private myOtherStore = resolve(IMyOtherStore),
    ) {
        super(props, context);
    }
    // ...
}

Registering Values

The container makes it possible to register values as well.

class MyObject {
    public MyValue: string;
}

container.registerValue(MyObject, { MyValue: "value" });
const x = resolve(MyObject);
expect(x.MyValue).toBe("value");

Registering with a String Key

It's also possible to register anything with a string key instead of a type key. Although in this case, there's no implicit typing.

container.registerValue("my number", 5);
const x = resolve<number>("my number"); // Explicit typing.
expect(x).toBe(5);

Registration Scopes

There are two registration scopes -- "permanent" and "transient". The permanent scope will create and keep one instance, whereas the transient scope will create a new instance everytime you resolve. The new instances will have all their dependencies correctly injected, so it is possible to mix and match lifetime scopes as you wish.

abstract class ObjectWithNumValue {
    public abstract MyValue: number;
}

class MyTransientObject implements ObjectWithNumValue {
    static Counter = 0;
    public MyValue: number;

    constructor() {
        this.MyValue = MyTransientObject.Counter++;
    }
}

container.register(ObjectWithNumValue, MyTransientObject, "transient");

const x = resolve(ObjectWithNumValue);
const y = resolve(ObjectWithNumValue);

expect(x.MyValue).not.toBe(y.MyValue);

Implicitly typed Constructor Parameters

If you need to provide constructor parameters when registering a type, you can do so using the third parameter of the "register" function, which implicitly infers the type from the provided implementation, so you can never mistakenly provide incorrect parameters.

abstract class IMyTuple {
    public abstract C5: Class5;
    public abstract get Tuple(): [string, number];
}

class MyTuple implements IMyTuple {
    constructor(
        private String: string,
        private Number: number,
        public C5 = resolve(Class5),
    ) { }

    public get Tuple(): [string, number] {
        return [this.String, this.Number];
    }
}

container.register(Class5, Class5, "permanent");
container.register(IMyTuple, MyTuple, "permanent", ["Str", 6]);

const x = resolve(IMyTuple);

expect(x.Tuple[0]).toBe("Str");
expect(x.Tuple[1]).toBe(6);
expect(x.C5.Field).toBe("5");

Simple Mocking and Testing

Leveraging default parameters in the constructor makes it very easy to shut off the injection mechanism in tests and simply provide the constructor with your own mock implementation.

class Class5Mock extends Class5 {};

new Class3(new Class5Mock()); // The default parameter assignment that calls "resolve" is overriden by the provided mock.