Giter VIP home page Giter VIP logo

first-di's Introduction

First DI

Easy dependency injection for typescript applications

Installation

For the latest stable version:

npm i first-di

Features

  • Easy and powerful dependency injection for any typescript application.
  • 2 modes of work. Optional DI - for most apps. Classic DI - for advanced apps.
  • Support for multiple scopes.
  • Supports multiple life cycles.
  • Dependency Free. Dependency used only for development.

Setup

Install reflect-metadata package and import in root typescript file. This package is needed to support reflection and is a mandatory requirement of Typescript.

In tsconfig.json enable compiler options:

{
    "compilerOptions": {
        ...
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true,
        ...
    }
}

Using in Optional DI mode

Just write classes and inject dependencies through class constructors. When the 'resolve' function is called, all dependencies will be resolved.

import { resolve, override, reflection } from "first-di";

@reflection // Typescript will generate reflection metadata
class ProdRepository { // Default implementation

    public async getData (): Promise<string> {
        return Promise.resolve("production");
    }

}

@reflection
class MockRepository implements ProdRepository { // Mock implementation with same interface

    public async getData (): Promise<string> {
        return Promise.resolve("mock");
    }

}

@reflection
class ProdService {

    public constructor (
        private readonly prodRepository: ProdRepository
    ) { }

    public async getData (): Promise<string> {
        return this.prodRepository.getData();
    }

}

@reflection
class ProdStore {

    public constructor (
        // Inject dependency
        private readonly prodService: ProdService
    ) {
        // Other logic here
    }

    public async getData (): Promise<string> {
        return this.prodService.getData();
    }

}

if (process.env.NODE_ENV === "test") { // Override in test environment
    override(ProdRepository, MockRepository);
}

const store = resolve(ProdStore); // Create intance by framework
const data = await store.getData();

if (process.env.NODE_ENV === "test") {
    assert.strictEqual(data, "mock");
} else {
    assert.strictEqual(data, "production");
}

Using in Classic DI mode

In professional mode Interfaces are used instead of implementations. But typescript does not generate Interfaces for working in runtime. But Interface is abstract base class. So instead of Interfaces, you need to write Abstract classes.

import { resolve, override, reflection } from "first-di";

abstract class AbstractRepository { // Abstract instead of interface

    public abstract getData (): Promise<string>;

}

@reflection
class ProdRepository implements AbstractRepository {

    public async getData (): Promise<string> {
        return Promise.resolve("production");
    }

}

@reflection
class MockRepository implements AbstractRepository {

    public async getData (): Promise<string> {
        return Promise.resolve("mock");
    }

}

abstract class AbstractService { // Abstract instead of interface

    public abstract getData (): Promise<string>;

}

@reflection
class ProdService implements AbstractService {

    private readonly prodRepository: AbstractRepository;

    public constructor (prodRepository: AbstractRepository) {
        this.prodRepository = prodRepository;
    }

    public async getData (): Promise<string> {
        return this.prodRepository.getData();
    }

}

@reflection
class ProdStore {

    public constructor (
        private readonly prodService: AbstractService
    ) {}

    public async getData (): Promise<string> {
        return this.prodService.getData();
    }

}

override(AbstractService, ProdService);

if (process.env.NODE_ENV === "test") {
    override(AbstractRepository, MockRepository);
} else {
    override(AbstractRepository, ProdRepository);
}

const store = resolve(ProdStore);
const data = await store.getData();

if (process.env.NODE_ENV === "test") {
    assert.strictEqual(data, "mock");
} else {
    assert.strictEqual(data, "production");
}

Options

First DI has several points for customizing dependency options:

  • Global - DI.defaultOptions: AutowiredOptions. Sets global default behavior.
  • Override - override(fromClass, toClass, options?: AutowiredOptions). Sets behavior overrided dependency.
  • Resolve - resolve(class, options?: AutowiredOptions). Sets behaviors for resolve dependencies.

Options has next properties:

  • lifeTime: AutowiredLifetimes - Sets lifeTime of dependecy.

    SINGLETON - Create one instance for all resolvers.

    PER_INSTANCE - Create one instance for one resolver instance. Also called ‘transient’ or ‘factory’ in other containers.

    PER_OWNED - Create one instance for one type of resolver.

    PER_ACCESS - Create new instance on each access to resolved property.

Scopes

Support multiple scopes

import { DI } from "first-di";
import { ProductionService } from "../services/ProductionService";

const scopeA = new DI();
const scopeB = new DI();

const serviceScopeA = scopeA.resolve(ProductionService);
const dataA = await serviceScopeA.getData();

const serviceScopeB = scopeB.resolve(ProductionService);
const dataB = await serviceScopeB.getData();

API

First DI also has an API for extended use.

  • override - Function. Override dependency and resolve options.
  • resolve - Function. Resolves dependence with default options or specified.
  • singleton - Function. Resolve singleton.
  • instance - Function. Resolve new instance.
  • reset - Function. Reset all singleton list and override list, but don.t reset global options.

Resolve, singleton, instance - can be used to implement the Service Locator.

import { singleton, instance, resolve, AutowiredLifetimes } from "first-di";

class ApiDemo {

    private readonly service3: ApiService3 = resolve(ApiService3, { lifeTime: AutowiredLifetimes.PER_INSTANCE });

    private readonly service4: ApiService4 = singleton(ApiService4);

    private readonly service5: ApiService5 = instance(ApiService5);

}

Extension DI

First DI using OOP and SOLID design principles. Each part of DI can be override or extende after inheritance from base class.

import { DI } from "first-di";

class MyDI extends DI {
    // extended method
    public getAllSingletons(): IterableIterator<object> {
        return this.singletonsList.values();
    }
}

first-di's People

Contributors

labeg avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

Forkers

sorteam qwinitron

first-di's Issues

Class field decorated with @autowired is undefined

Expected behavior

  @autowired()
  private readonly myService!: AService;

  private readonly myStr: string;

  constructor() {
     this.myStr = "test";
  }

  public foo():string {
      return this.myService.getObj();
  }

Call of foo method returns string

Actual behavior

this.myService is undefined insade foo method

  @autowired()
  private readonly myService!: AService;

  private readonly myStr: string;

  constructor() {
     this.myStr = "test";
  }

  public foo():string {
      return this.myService.getObj();
      //TypeError: Cannot read properties of undefined
  }

Steps to reproduce behavior:

  1. typescript version >=3.7
  2. set useDefineForClassFields: true in tsconfig.json

0.1.38 doesn't work (webpack5)

WARNING:
export 'reflection' (imported as 'reflection') was not found in 'first-di' (possible exports: autowired, instance, override, reset, resolve, singleton)

ERROR in ./node_modules/first-di/dist/index.mjs 3:0-45
Module not found: Error: Can't resolve './models/autowired-lifetimes' in '...\node_modules\first-di\dist'
Did you mean 'autowired-lifetimes.js'?
BREAKING CHANGE: The request './models/autowired-lifetimes' failed to resolve only because it was resolved as fully specified
(probably because the origin is a '.mjs' file or a '.js' file where the package.json contains '"type": "module"').
The extension in the request is mandatory for it to be fully specified.
Add the extension to the request.

Problems on build time

Trying to use this lib as described in readme, but got this error on build time:

> Build error occurred
/node_modules/first-di/dist/index.js:1
export * from "./classes/di";
^^^^^^

SyntaxError: Unexpected token 'export'

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.