1.0.2 • Published 12 months ago

ioc-class v1.0.2

Weekly downloads
-
License
MIT
Repository
github
Last release
12 months ago

Project Title

Simple dependency injection library for Typescript

Features

  • Dependency injection using type hinting
  • Support for singleton and transient dependencies
  • Support to run on both browser and server
  • No dependencies except reflect-metadata

Installation

This library uses reflect-metadata for dependency resolution, so you have to install it also.

npm install reflect-metadata
npm install ioc-class

Usage/Examples

Import the reflect-metadata library on the project entry point.

// index.ts

import "reflect-metadata";

Declare some dependencies

class BookService {}

interface IShelfService {}

class ShelfService implements IShelfService {
	public constructor(
		private readonly bookService: BookService,
	) {}
}

const ShelfServiceToken = new Token<IShelfService>("ShelfService");

class LibraryService {
	public constructor(
		@Inject(ShelfServiceToken) private readonly shelfService: IShelfService,
	) {}
}

Initialize the container

const container: IContainer = new Container();

Register the dependencies in the container

// Order of registration doesn't matter
container.registerSingleton(LibraryService);
container.registerSingleton(ShelfServiceToken, ShelfService);
container.registerSingleton(BookService);

Resolve them anywhere

// Dependencies that are registered via token must be resolved via the same token
const shelfService: IShelfService = container.resolve(ShelfServiceToken, ShelfService);

// Dependencies that aren't registered via token must be resolved by passing themselves
const libraryService: LibraryService = container.resolve(LibraryService);
const bookService: BookService = container.resolve(BookService);

API Reference

Initialize container

import { Container, IContainer } from "ioc-class";

const container: IContainer = new Container();

Register a singleton dependency

class UserService {}

container.registerSingleton(UserService);

container.resolve(UserService) // UserService

Register a singleton dependency using the resolution token

import { Token } from "ioc-class";

interface IUserService {}

class UserService implements IUserService {}

const UserServiceToken = new Token<IUserService>("UserService");
container.registerSingleton(UserServiceToken, UserService);

container.resolve(UserServiceToken); // UserService

Register a transient dependency

class UserService {}

container.registerTransient(UserService);

container.resolve(UserService) // UserService

Register a transient dependency using the resolution token

import { Token } from "ioc-class";

interface IUserService {}

class UserService implements IUserService {}

const UserServiceToken = new Token<IUserService>("UserService");
container.registerTransient(UserServiceToken, UserService);

container.resolve(UserService) // UserService

Inject a dependency using the resolution token

import { Inject, Token } from "ioc-class";

interface IBookService {}

class BookService implements IBookService {}

const BookServiceToken = new Token<IBookService>("BookServiceToken");

class UserService {
	public constructor(
		@Inject(BookServiceToken) private readonly bookService: IBookService,
	) {}
}

container.registerSingleton(BookServiceToken, BookService);
container.registerSingleton(UserService);

container.resolve(UserService); // UserService

FAQ

Can it resolve type hinted dependency when used as typed imports?

No

Can I add a proxy instance on a dependency?

Yes, but there is a catch. Consider an example where the constructor initialization is trapped using proxies:

const Decorator = <T>(target: Constructable<T>): Constructable<T> => {
	return new Proxy(target, {
		construct(concrete: Constructable<T>, args: Array<any>) {}
	})
}

class DemoService {}

@Decorator
class UserService {
	public constructor(
		private readonly demoService: DemoService,
	) {}
}

const container = new Container();

container.registerSingleton(UserService);
container.registerSingleton(DemoService);

const userService = container.resolve(UserService);

Here the UserService instance will be created, but demoService will be undefined inside of userService. That is because when we returned the proxified constructor from the Decorator function, the metadata properties from UserService are lost.

To work around this issue, use the helper function copyMetadata like this:

import { copyMetadata } from "ioc-class";

const Decorator = <T>(target: Constructable<T>): Constructable<T> => {
	const proxifiedTarget = new Proxy(target, {
		construct(concrete: Constructable<T>, args: Array<any>) {}
	});

	// This will copy the metadata properties from the original class constructor to the proxified one.
	copyMetadata(target, proxifiedTarget);

	return proxifiedTarget;
}