Coder Social home page Coder Social logo

typestack / typedi Goto Github PK

View Code? Open in Web Editor NEW
3.9K 29.0 161.0 4.29 MB

Simple yet powerful dependency injection tool for JavaScript and TypeScript.

Home Page: https://docs.typestack.community/typedi/v/develop/01-getting-started

License: MIT License

TypeScript 99.05% JavaScript 0.95%
dependency-injection typescript inversion-of-control ioc typedi

typedi's Introduction

TypeDI

Build Status codecov npm version Dependency Status

TypeDI is a dependency injection tool for TypeScript and JavaScript. With it you can build well-structured and easily testable applications in Node or in the browser.

Main features includes:

  • property based injection
  • constructor based injection
  • singleton and transient services
  • support for multiple DI containers

Installation

Note: This installation guide is for usage with TypeScript, if you wish to use TypeDI without Typescript please read the documentation about how get started.

To start using TypeDI install the required packages via NPM:

npm install typedi reflect-metadata

Import the reflect-metadata package at the first line of your application:

import 'reflect-metadata';

// Your other imports and initialization code
// comes here after you imported the reflect-metadata package!

As a last step, you need to enable emitting decorator metadata in your Typescript config. Add these two lines to your tsconfig.json file under the compilerOptions key:

"emitDecoratorMetadata": true,
"experimentalDecorators": true,

Now you are ready to use TypeDI with Typescript!

Basic Usage

import { Container, Service } from 'typedi';

@Service()
class ExampleInjectedService {
  printMessage() {
    console.log('I am alive!');
  }
}

@Service()
class ExampleService {
  constructor(
    // because we annotated ExampleInjectedService with the @Service()
    // decorator TypeDI will automatically inject an instance of
    // ExampleInjectedService here when the ExampleService class is requested
    // from TypeDI.
    public injectedService: ExampleInjectedService
  ) {}
}

const serviceInstance = Container.get(ExampleService);
// we request an instance of ExampleService from TypeDI

serviceInstance.injectedService.printMessage();
// logs "I am alive!" to the console

Documentation

The detailed usage guide and API documentation for the project can be found:

Contributing

Please read our contributing guidelines to get started.

typedi's People

Contributors

2betop avatar asvetliakov avatar attilaorosz avatar bbakhrom avatar binki avatar bruno-brant avatar bsitruk avatar codemilli avatar dependabot[bot] avatar digitalkaoz avatar gjdass avatar happyzombies avatar jrel avatar klassm avatar nightink avatar nonameprovided avatar pepakriz avatar petermetz avatar pleerock avatar sobolevn avatar suchcodemuchwow avatar timwolla avatar tonivj5 avatar zibanpirate 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  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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 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

typedi's Issues

Help - I have some problems with unit test

I use TypeDI to integrate routing-controllers(express) and TypeORM(with extensions).

I know how to test routes but
How can I do a unit test with a service or custom repository?

I do this

    @Service()
    class Injector {
        @OrmRepository()
        userRepository: UserRepository;
    }

    let injector: Injector;

    before(() => {
        injector = Container.get(Injector);
    });

but failed

EntityMetadataNotFound: No metadata for "User" was found.

it seems the TypeORM is still under initialization when the test case wants to get object from TypeDI.

IE 11 support?

Hi.
I have a simple test project and it perfectly works in all browsers except IE11.
What do I need to do to be able to use 'typedi' in IE11? Is it possible?
I already tried to include es6-shim but it doesn't help.

Right now code is looks like this:

import "reflect-metadata";
import {Container, Service} from "typedi";
import Some from "./some";

@Service()
export default class TestClass {

    constructor(private some: Some, private some2: Some){
    }

    test1() {
        this.some.someMethod();
        this.some2.someMethod();
    }
}


Container.get<TestClass>(TestClass).test1();

And only in IE11 I have 'undefined' for both 'some' and 'some2' in TestClass constructor.

Thanks.

Exception not thrown on missing binding

Right now the behavior is to inject undefined when a service requests an absent binding. This defeats the purpose of a type system as it opens the door for sneaky errors in the middle of a sound method.

After doing some research, I found that it's most likely a bug which happens when requiring a service by its type:

Here is what I mean expressed as a test case:

it("should throw if the object was not set", () => {
    class Unregistered { }
    // Both assertions should pass but only the first one does:
    expect(() => Container.get("unregistered")).to.throw();
    expect(() => Container.get(Unregistered)).to.throw();
});

@Inject in customRepository seems no work

I have a service

@Service()
export class Discuz {
  ...
}

While I want to inject it in customRepository, however it seems no work.

@Service()
@EntityRepository(User)
export class UserRepository extends Repository<User> {
  @Inject() 
  discuz: Discuz
  
  async loginDiscuz (cookies: {}, body: {}) {
    console.log(this.discuz, Container.get(Discuz)) // This output undefined Discuz {}
  }
}

But this work:

@Service()
export class UserService {
  @OrmCustomRepository(UserRepository)
  userRepository: UserRepository

  @Inject() 
  discuz: Discuz

  async loginDiscuz (cookies: {}, body: {}): Promise<{ accessToken: string }> {
    console.log(this.discuz) // output Discuz {}
    return this.userRepository.loginDiscuz(cookies, body)
  }
}

Maybe @EntityRepository breaks the typedi?

Token as service id in combination with factory

For now it's not possible to use Token as service id in combination with factory, like this:

interface SomeInterface {
    foo();
}
const SomeInterfaceToken = new Token<SomeInterface>();

@Service()
class SomeInterfaceFactory {
    create() {
        return new SomeImplementation();
    }
}

@Service({ id: SomeInterfaceToken, factory: [SomeInterfaceFactory, 'create']})
class SomeImplementation implements SomeInterface {}

ServiceOptions, that i suppose, used in background typedi require id as string.

question: does the @Service decorator required?

Is @Service always required for constructor injection like:

@Service() // <===== this
class CoffeeMaker {

    private beanFactory: BeanFactory;
    private sugarFactory: SugarFactory;
    private waterFactory: WaterFactory;

    constructor(beanFactory: BeanFactory, sugarFactory: SugarFactory, waterFactory: WaterFactory) {
        this.beanFactory = beanFactory;
        this.sugarFactory = sugarFactory;
        this.waterFactory = waterFactory;
    }
}

let coffeeMaker = Container.get<CoffeeMaker>(CoffeeMaker);

coffeeMaker.make();

seems odd, but maybe you need it for something under the hood?

TypeError: connectionManager.has is not a function

Hi there,
After calling localhost:4001/, i get this error in my console:

C:\Applications\Node\exotica\server>node app.js
Server is up and running at port 3000
TypeError: connectionManager.has is not a function
at Object.value (C:\Applications\Node\exotica\node_modules\typeorm-typedi-extensions\decorators\OrmCustomRepository.js:13:40)
at C:\Applications\Node\exotica\node_modules\typedi\Container.js:171:32
at Array.forEach (native)
at Function.Container.applyPropertyHandlers (C:\Applications\Node\exotica\node_modules\typedi\Container.js:162:23)
at Function.Container.get (C:\Applications\Node\exotica\node_modules\typedi\Container.js:68:14)
at C:\Applications\Node\exotica\node_modules\typedi\Container.js:148:34
at Array.map (native)
at Function.Container.initializeParams (C:\Applications\Node\exotica\node_modules\typedi\Container.js:143:27)
at Function.Container.get (C:\Applications\Node\exotica\node_modules\typedi\Container.js:48:48)
at Object.getFromContainer (C:\Applications\Node\exotica\node_modules\routing-controllers\container.js:36:42)

ะะต ะฟะพะปัƒั‡ะฐะตั‚ัั ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ

ะฟะธัˆัƒ ั‚ะฐะบ

///<reference path="Render.ts" />
import {Container} from "typedi";
class SomeClass {
    someMethod() {
        console.log('test1');
    }
}

let someClass = Container.get(SomeClass);
someClass.someMethod();`

ะšะพะผะฟะธะปะธั€ัƒัŽ ะฒ phpstorm
ะฝะฐ ะฒั‹ั…ะพะดะต

var Template;
(function (Template) {
    var Render = (function () {
        function Render() {
        }
        Render.prototype.test = function () {
        };
        return Render;
    }());
    Template.Render = Render;
})(Template || (Template = {}));
//# sourceMappingURL=Main.js.map

ะŸะพะปัƒั‡ะธะปะพััŒ ั‡ั‚ะพ ัะบะพะผะฟะธะปะธะปัั ั‚ะพะปัŒะบะพ ะผะพะน ั„ะฐะนะป
packege.json
"build-js": "browserify src/Main.js > main.js | browserify node_modules/typedi/index.js > typeid.js",

ะŸะพะดะบะปัŽั‡ะฐะป ั‚ะฐะบ

 <script type="text/javascript" src="typeid.js"></script>
    <script type="text/javascript" src="main.js"></script>

Token-based services are cached in the Global container even when fetched via a subcontainer

This is in current master branch (6876e60):

Based on example 13, the following code:

import "reflect-metadata";
import { Container, Service, Token } from "../../src/decorators/Service";

@Service()
export class QuestionRepository {
    userName: string;

    save() {
        console.log(`saving question. author is ${this.userName}`);
    }

}

export const QuestionController = new Token<QuestionControllerImpl>("QCImpl");

@Service({ id: QuestionController })
export class QuestionControllerImpl {

    constructor(protected questionRepository: QuestionRepository) {
    }

    save(name: string) {
        if (name)
            this.questionRepository.userName = name;
        this.questionRepository.save();
    }
}

const request1 = { param: "Timber" };
const controller1 = Container.of(request1).get(QuestionController);
controller1.save("Timber");
Container.reset(request1);
// Container.removeFromRequest(request1, QuestionController);

const request2 = { param: "Guest" };
const controller2 = Container.of(request2).get(QuestionController);
controller2.save("");
Container.reset(request2);

console.log("Equivalent controllers", controller1 === controller2);
console.log("Equivalent to global", controller1 === Container.get(QuestionController));

The above code produces the following output:

$ ./node_modules/.bin/ts-node sample/sample13-multiple-containers/app.ts
saving question. author is Timber
saving question. author is Timber
Equivalent controllers true
Equivalent to global true

Expected output:

$ ./node_modules/.bin/ts-node sample/sample13-multiple-containers/app.ts                                                [0]-[1s/1541]โ”˜
saving question. author is Timber
saving question. author is undefined
Equivalent controllers false
Equivalent to global false

Is it possible to use named params?

I really dig this library, but I'm missing named params from Javascript land (I still consider myself only at like 45% with Typescript).

If I have a service such as:

@Service()
export class WebhookService {

  constructor(
    @OrmRepository() private borrowerRepository: BorrowerRepository,
    @OrmRepository() private webhookRepository: WebhookRepository,
    private client: ClientService,
    @Logger(__filename) private log: ILogger
  ) { }

My tests for this become clunky for instantiating:

const service = new WebhookService(borrowerRepoMock, webhookRepoMock, clientServiceMock, logMock);

It would be nice if I could use named params so order wouldn't matter:

const service = new WebhookService({
  borrowerRepo: borrowerRepoMock,
  webhookRepo: webhookRepoMock,
  clientService: clientServiceMock,
  log: logMock
});

Do you have any sample patterns to achieve this cleanly while still maintaining the dependency injected behavior?

Add ability to clear the container

As discussed here: #11 (comment) we need a way to clear the "dirty" container effectively removing all registered services, instances and param handlers. This will allow us to have more isolated unit tests.

need to use require to be able to have services

hey, another weird issue:

// services.ts
import {Token, Service, Inject} from 'typedi'

export declare interface IFoo {
  foo(): void
}

const FooImpl = new Token<IFoo>()

@Service(FooImpl)
export class Foo implements IFoo {
  foo (): string {
    return 'foo'
  }
}

@Service('bar')
export class Bar {

  @Inject(FooImpl.name)
  public foo: Foo
}

use case 1.

//main.ts
import 'reflect-metadata'

import {Container} from 'typedi'
console.log(Container.services) //made it public for testing

-> nothing inside services

use case 2.

//main.ts
import 'reflect-metadata'

import {Container} from 'typedi'
import {Foo, Bar} from './services'

console.log(Container.services) //made it public for testing

-> nothing inside services

use case 3.

//main.ts
import 'reflect-metadata'

import {Container} from 'typedi'
require('./services')

console.log(Container.services) //made it public for testing

-> all services from services.ts are available in container

personally i would prefer 1. but that seems to be unpossible i think
2. should work, i dunno why, maybe tsc optimizes the unused import away?!
3. is okish, but should be documented i think

Multiple tagged services injection using decorators

Hello!

Is it possible right now to inject multiple tagged services using constructor injection and decorators?

Example:

@Service()
class Foo {
  constructor (@Inject(TaggedService) TaggedService[] services) {
  }
}

Or do we need something like @InjectMany?

Thanks.

Using a Factory to Create Services

Hello!

Thank you for this great module!

However, is it possible to instantiate services by calling a factory function instead of the constructor of the specified class?

I know, you can use Container.provide() to add instances directly to the container, but this will force us to instantiate all instances during container initialization stage. The better approach would be to provide a factory function, which will be called by the container when the instance is first requested from it (i.e. lazy instantiation).

Here's the example of the desired code:

// The "Bus" class is registered, but not yet instantiated.
// Factory function is specified by the "factory" property.
Container.provide([{
  type: Bus,
  factory: BusFactory.create
}]);

// The "Bus" class is instantiated by calling
// the "BusFactory.create" factory, then instance is returned.
// Without this call, "Bus" will never be instantiated.
const bus = Container.get(Bus);

bus.drive();

// Bus factory is responsible for creating new buses.
class BusFactory {
    // Logger instance should be injected by DI container automatically.
    public static create (Logger: logger) {
        logger.debug('Creating a new Bus');
        return new Bus();
    }
}

class Bus {
    constructor (private Logger: logger) {
        logger.debug('Bus is created');
    }
    drive () {
        this.logger.debug('Bus is moving');
    }
}

Also, the factory function itself must be able to rely on values injected by DI container.

Right now, I'm using Angular 2 DI Container in my Node.js application (it supports factory functions), but it's API is somewhat cumbersome and limited. I really like the API of your Container, especially the use of annotations, but lack of the factory functions support is stopping me from migrating.

Also, here's how this implemented in Symfony: Using a Factory to Create Services.

Are you planning to add support for factory functions? Or have I somehow missed it? Or could you recommend some workarounds?

Thanks!

Lifecycle hook

class SomeService {
  @Inject(ConfigService) config: Config
  cache = new SomeCache(config.cacheConfig)
}

configis injected after constructor and this will result in undefined error

Provide a method to do some initialization after dependency injected. For example:

class SomeService {
  @Inject(ConfigService) config: Config

  @AfterInit
  init() {
    this.cache = new SomeCache(config.cacheConfig)
  }
}

Service decorator name

From @Service JSdoc:

Marks class as a service that can be injected using container.

So it works like in Angular Injector and Inversify, so maybe it would be better to rename it to @Injectable like in this two DI?

@Service could be deprecated and exported as an alias for @Injectable for now.

register service with token and @Service(Token)

In the sample8-tokenized-services the services are registered via Container.set. I tried it with a @Service(StoreService) decorator at FtpStore, but that don't work.

Shouldn't I be able to register services with a token via a decorator like stated in the readme?

https://github.com/typestack/typedi#services-with-token-name

ServiceNotFoundError: Service with a given token was not found, looks like it was not registered in the container. Register it by calling Container.set before using service.

Injecting restify server

Hi,

I'd like to inject the Server created by restify.createServer().

It should look something like this:
Container.set(restify.Server, server);

I'm providing the instance once the createServer() promise is resolved successfully.

The problem is, Server is an interface and not a class, it is not described as a class anywhere in restify's definition file.

Naturally, I'm able to access the Server instance's prototype, but it is not described via typescript anywhere.

How would you suggest I should inject it?

Even if providing the instance as follows would work (I don't know if it would):
Container.set((server as any).prototype, server);

I won't be able to do something like this in the class I'm injecting into (since here Server would be referring to an interface):

@Inject()
server: Server;

Would named services be the solution? Is there a way to do it without them?

Simple question about how to expose external "require" as services

npm install bcryptjs --save
npm install @types/bcryptjs --save

I'd like to expose the bcryptjs utility as a service. Is this possible? Right now i'm using:

import * as bcrypt from 'bcryptjs'

... in my controller, but this doesn't feel "right". The same for other external libraries.

Thanks so much.

introduce multiple set method

    Container.set({
        "koa-app": app,
        "koa-server": server
    });

same as Container.set("koa-app", app) and Container.set("koa-server", server)

getMany returning empty array

I'm having an issue where Container.getMany(TOKEN) is returning an empty array, despite having {id: TOKEN, multiple: true} services in my project.

I have also tried with @InjectMany(TOKEN) to no avail.

Example repo with reproduction: https://github.com/j3ddesign/typedi-getMany-example

EDIT: as a sanity check I also replicated the example at https://github.com/typestack/typedi#using-service-groups - This also failed to get the services.
I've tried looking through the source code of typedi to see if I can PR this, but currently it's a bit over my head. Would love a fix, this is a great feature

Register service using interface as type / Typing for named services

Another issue, I've found when migrating from Angular Injector is that there is no way to register service using interface as a type.

The problem is that interfaces are design-time only artifacts, which are deleted from compiled code altogether. So there is no way to reference the service by it's interface name at runtime.

Angular approached this problem by providing a special class called InjectionToken. The workflow is the following: first, you create an instance of the injection token: export const DI_SEQUELIZE = new InjectionToken<Sequelize>('sequelize');, then you use it to register service with the container. By using such hint, typescript can correctly track types of the instances returned from the container.

Right now, if I register a named service and then get it, the return type is not inferred, so I have to manually cast it to a correct type, which makes code harder to read and maintain.

@pleerock What could be done about this?

Token service iDs in global container aren't inherited by scoped containers

If a service is registered with the global container instance using a token, a ServiceNotFound error is thrown when calling scopedContainer.get(ServiceToken).

import {Container, Service, Token} from "typedi";

interface FooService {
    marco(): void;
}

const FooServiceToken = new Token<FooService>();

// @Service({ id: FooServiceToken, factory: () => new FooServiceI() }) <= Providing a factory does not work either
@Service(FooServiceToken)
class FooServiceI implements FooService {
    public marco() {
        console.log("polo");
    }
}

Container.get(FooServiceToken).marco(); // Works

const scopedContainer = Container.of({});

scopedContainer.get(FooServiceI).marco(); // Works

scopedContainer.get(FooServiceToken).marco(); // throws ServiceNotFoundError

an instance can be retrieved from the scoped container if the type of the desired implementation is passed, but not the token under which the service for that type was registered.

Using typedi version 0.6.0

Better Interface Support

It would be nice if one could do something like this:

import { Container } from 'typedi/Container';

import { IRecordRepository } from '../repository/IRecordRepository';
import { RecordRepository } from '../repository/neo4j/RecordRepository';

Container.provide([
  { name: 'RecordRepository', type: IRecordRepository, value: new RecordRepository() }
]);

then later do:

@Controller('/app/:appId/record')
export default class RecordController {

  @Inject()
  recRepository: IRecordRepository;

}

notice how I pointed it using interfaces vs a naming technique. Is this possible?

Providing values to the constructor/factory

Hello,

maybe I am getting something wrong, but I am looking for a way to provide a parameter to the constructor. My use case is, that I would like to inject a logger, that should be instantiated with a module name, e.g. const logger = new Logger('moduleX');

That I would like to change to work like this

class ModuleX {

  @Inject
  private logger: Logger;

  public doSmth () {
    
    this.logger.info('log something');
  }
}

Obviously this is not supported and therefor I assume not a good practice, or am I missing something?

thank you!

Constructor inject not working

Hei, i'm experiencing weird behaviour in my tests with constructor injects

//..
import {Container} from 'typedi';
import {AccessTokenService} from 'src/authentication/services/access-token-service';
import * as cfg from 'src/application/config';
Container.set('cfg.auth.jwt',cfg.auth.jwt);
const accessTokenService = Container.get(AccessTokenService);
accessTokenService.makeAccessToken(user);
//..

Dependecies are undefined when i configure them in the constructor

import {Service, Inject, Require} from 'typedi';
// ..
@Service('AccessTokenService')
export class AccessTokenService {

    constructor(
        @Require('moment') private moment,
        @Require('jsonwebtoken') private jsonwebtoken,
        @Inject('cfg.auth.jwt') private jwt
    ) {}

    makeAccessToken(user: User) {
        console.log(typeof this.jsonwebtoken); //undefined
        console.log(typeof this.jwt); //undefined
        console.log(typeof this.moment); //undefined
        // ...
    }
}

But this works

import {Service, Inject, Require} from 'typedi';
// ..
@Service('AccessTokenService')
export class AccessTokenService {

    @Require('moment') private moment;
    @Require('jsonwebtoken') private jsonwebtoken;
    @Inject('cfg.auth.jwt') private jwt;
  
    makeAccessToken(user: User) {
        console.log(typeof this.jsonwebtoken); //object
        console.log(typeof this.jwt); //object
        console.log(typeof this.moment); //function
        // ...
    }
}

Constructor dependency injection not working

I'm using the following code:

import {Service} from 'typedi';
import {OrmManager, OrmRepository} from 'typeorm-typedi-extensions';
import {EntityManager, Repository} from 'typeorm';
import {schools} from '../entities/schools';

@Service()
export class SchoolRepository {

  @OrmManager()
  private entityManager: EntityManager;

  constructor(@OrmRepository(schools) private ormRepository: Repository<schools>) {
  }

  saveUsingRepository(post: schools) {
    return this.ormRepository.save(post);
  }

  saveUsingManager(post: schools) {
    return this.entityManager.save(post);
  }

  findAll() {
    return this.ormRepository.find({cache: 60000, take: 100});
  }

}

Then I use this code the get this repository:

import {JsonController, Get, QueryParam} from 'routing-controllers';
import {SchoolRepository} from '../repository/SchoolRepository';
import {Container, Service} from 'typedi';

@Service()
@JsonController()
export class SchoolsController {

 constructor(private repository: SchoolRepository){
   //is null... :C
 }

  @Get("/schools")
  get(@QueryParam("page") page: number = 0) {
    return this.repository.findAll();
  }

}

The repository is always null, the only time when it returns the class is when I use this: Container.get(SchoolRepository);

Support @PostConstruct

Like Spring @PostConstruct, define a method which invoked after the object has been created and injected. So, we can do some init like a constructor.

Container.has()?

Hey!

Is there a way to check if container has an instance of the specific type w/o actually creating this instance?

E.g: if (Container.has(CarService)) {}.

Thanks )

How to inject Request/Session object into service classes ?

Hi there,
I don't know how to inject Request/Session object into service classes.
Here is what i do and does not work :

LocalisationFactory.ts:

import {Inject,Service} from "typedi";
import {Request} from "express";
import { Localisation } from "./Localisation";

@Service()
export class LocalisationFactory
{
    @Inject()
    request: Request;
    
    create()
    {
        let instance = new Localisation();
        instance.setRequest(this.request);               
        return instance;
    }
}

get/inject an array of service instances

Is there a way to inject all service instances (e.g. named services) into an array?
This is a "must have" feature, but I could not find anything in the docs.

Somehow when there are more than ~4 injections there is an error: cannot read property "bind" of undefined

0|Browser- | TypeError: Cannot read property 'bind' of undefined
0|Browser- |     at Function.Container.get

Removing dependency removes error.

Using like this:

const playerService = Container.get(PlayerService);

Does not help. Gives the same error.
Using at runtime gives the same error. (the funny thing, even not calling method directly but just writing Container.get there)

There are no circular dependencies between services.

Full error log:

0|Browser- | TypeError: Cannot read property 'bind' of undefined
0|Browser- |     at Function.Container.get (~/node_modules/typedi/Container.js:64:39)
0|Browser- |     at Object.getValue (~/node_modules/typedi/decorators.js:31:70)
0|Browser- |     at ~/node_modules/typedi/Container.js:110:40
0|Browser- |     at Array.forEach (native)
0|Browser- |     at Function.Container.applyPropertyHandlers (~/node_modules/typedi/Container.js:105:14)
0|Browser- |     at Function.Container.get (~/node_modules/typedi/Container.js:66:14)
0|Browser- |     at Object.getValue (~/node_modules/typedi/decorators.js:31:70)
0|Browser- |     at ~/node_modules/typedi/Container.js:110:40
0|Browser- |     at Array.forEach (native)
0|Browser- |     at Function.Container.applyPropertyHandlers (~/node_modules/typedi/Container.js:105:14)

Issues with scoped containers and Tokens

I have code similar to the following:

export interface TestService {
  sayHello(): void;
}
export const TestService = new Token<TestService>("TestService");

@Service({ id: TestService, factory: () => new TestServiceImpl() })
class TestServiceImpl {
  public sayHello() {
    console.log("Hello world!");
  }
}

Then I can't run the following:

Container.get(TestService).sayHello();
// Works.
Container.of({ foo: "bar" }).get(TestService).sayHello();
// ServiceNotFoundError: Service "TestService" was not found, looks like it was not registered in the container. Register it by calling Container.set before using service.

An explicit set in the scoped container fixes the issue but also duplicates the service definition.

Make containers named and instantiable

As discussed here: #11 (comment) we want to move from a single static container to a named instantiable ones.

Here's the desired API:

// Returns container instance of the default container:
const container1 = Container.get();

// Returns container instance of the second container.
// The additional container which goes under the "second-container" name.
const container2 = Container.get("second-container");

// This service belongs to the default container.
@Service()
class Foo {
}

// Or more meaningful API:
@Service({
  name: "bar",
  container: "second-container"
})
class Bar {
}

Move reflection logic from decorators to service layer

From: typestack/routing-controllers#116 (comment)

pleerock: But I think we can resolve this issue and move reflect-metadata read from @service decorator in some other place in the Container and check if it will work this way.

slavafomin: Considering moving reflection to the service layer โ€” I think it's the best way to go. We will have centralized place where container is configured and decorators could be very thin, so their functionality could easily be replicated in other "aggregating" decorators of sort. And the DI could be configured without using any decorators at all, which is good for classes coming from third-party libraries where you can't add decorators.

extended class is being overwritten

First off, I love this project, it's been incredible.
I'm having a slight issue and was wondering If I could get some insight as it's probably a me issue.
Below is a small snippet of what I have.

// Whitelist.ts
@Service()
export class Whitelist extends Rule {
  entries: Set<string>;

  setup(entries: WhitelistEntryFragment[]) {
    this.entries = new Set(entries.map(entry => entry.value));
  }
}
// rule.ts
@Service()
export class Rule {
  id: number;
  name: string;
  enabled: boolean;
  silent: boolean;
  settings: any;
  responses: Responses[];

  @Inject('logger')
  logger: LoggerInterface;

  static parseRule(rule: RuleFragment): RuleInterface {
    return Object.assign({}, rule, {
      responses: rule.responses.map<Responses>(response => response.value)
    });
  }
}

However when I run the following

const whitelist = Container.get(Whitelist);
console.log(whitelist);
whitelist.setup(
  data.channel.whitelist
);

The log returns only

Rule {
  logger:
   ConsoleLogger {
     loggingLevel: 'trace',
     trace: [Function],
     debug: [Function],
     info: [Function],
     warn: [Function],
     error: [Function],
     fatal: [Function] } }
[13:49] error: TypeError: whitelist.setup is not a function

Any help would be appreciated here. I'm guessing I'm missing some really basic concept here but I've been lost for about an hour now ๐Ÿ‘Ž

How it is supposed to work with unit test?

Hello there,
I would like to test my controller (routing-controllers) but i don't really know how to.
Typedi is supposed to work like Angular2+ inject or similar.
But when i use Container.set to override my model's method used by the controller it's seems to be broken.
I need some hint to know how to do that.
I think i do not test my controller like expected or maybe i don't really understand Typedi Container and @service injection implementation.
It's supposed to be usefull to avoid to mock db connection.

Thx a lot.

My spec

import 'reflect-metadata';
import { Application } from 'express';
import request from 'supertest';
import { createExpressServer, useContainer as useContainerRouting } from 'routing-controllers';
import { Container } from 'typedi';
import { ExampleController } from '../controllers/example.controller';
import { ExampleRepository } from '../repositories/example.repository';
import { ExampleModel } from '../models/example.model';

describe('GET', () => {
  let app: Application | null;

  beforeEach((_done: jest.DoneCallback) => {
    useContainerRouting(Container);
  });

  afterEach((_done: jest.DoneCallback) => {
    Container.reset();
    app = null;
  });

  it('should call model with proper id and returns model response', async () => {
    const EXAMPLE_ID_TEST: number = 1;

    app = createExpressServer({
      cors: true,
      controllers: [
        ExampleController
      ]
    });

    @Service()
    class TestModel {
      public getExampleById: any = jest.fn().mockImplementation((_id: number) => new Promise((resolve: any) => resolve({})));
    }

    Container.set(ExampleModel, new TestModel());

    const res: request.Response = await request(app).get(`/example/${EXAMPLE_ID_TEST}`);

    expect(res.body).toEqual({});
});

My controller

import { ExampleModel } from '../models/example.model';
import { TableTest } from '../entities/tabletest.entity';
import { JsonController, Get } from 'routing-controllers';

@JsonController('/example')
export class ExampleController {
  constructor(
    private readonly model: ExampleModel) {
  }
  @Get('/:id')
  public async getExample(@Param('id') id: number): Promise<TableTest> {
    return this.model.getExampleById(id);
  }
}

My Model

import { TableTest } from '../entities/tabletest.entity';
import { Service } from 'typedi';
import { OrmRepository } from 'typeorm-typedi-extensions';
import { NotFoundError } from 'routing-controllers';
import { ExampleRepository } from '../repositories/example.repository';

@Service()
export class ExampleModel {
  constructor(
    @OrmRepository()
    private readonly repository: ExampleRepository) {
  }

  public async getExampleById(id: number): Promise<TableTest> {
    const example: TableTest | undefined = await this.repository.findOneById(id);

    if (example === undefined) {
      throw new NotFoundError(`Example ${id} not found`);
    }

    return example;
  }
}

Request: additional options for injecting provider values

Angular DI offers several options for providing dependencies to the container:

[
  { provide: ..., useClass: FakeLogger },
  { provide: ..., useExisting: Logger},
  { provide: ..., useValue: { log: () => {} } },
  { provide: ..., useFactory: createLogger }
]

In particular, it would be great if we could provide a factory function. I think this would be much better than the existing solution that requires you use @Service({ factory: ... }). With the factory defined in the service decorator it's much more difficult to mock out in tests.

Container.provide([
  { name: 'foo', value: new Foo() },
  { name: 'foo.class', class: Foo },
  { name: 'foo.factory', factory: () => new Foo() },
])

Throw/Exit when dependency can't be resolved

This is a proposal of feature. Should it get attention I can work on this.

The problem is simple, I think that when dependency can't be satisfied (either it doesn't exist or one of its own dependencies wasn't found) the DI container should exit saying it couldn't build the dependency tree or throw an error. When using string-based tokens it is easy to miss something.

Not doing that simply leads to very obscure errors where seemingly valid TS code throws random "undefined" errors in runtime.

What do you think?

Accessing sources from npm package

Hello!

I have a project in which I use typedi through npm. The npm-package includes source-map files for all .js files. However, those source maps reference original files like this: ../../src/Container.ts, so this path leads outside of node_modules directory and points to non-existent source files.

I'm using source-map-support module to make stack traces more readable, but the source maps of typedi make reported paths weird. It tries to resolve them to files in my project. This complicates debugging.

Actually, I would love those stack traces to be resolved back to original TypeScript files, but sadly the npm-package doesn't contain source files at all. If source files were present it would allow to much easier trace and resolve errors in projects and even to use debugger to dive into internals of typedi when used with the real project (this really helps with integration).

So, I think there could be two courses of action:

1). Add source .ts files to the npm-package.

2). Remove source maps from npm package because they point to inexistent files.

Does it makes sense?

Thank you.

Create Factory services

Hi,
What's the correct way to use factory services.
Usually, (in my Php projects), i'm using a service locator.

Ex:

public function createService(ServiceLocatorInterface $serviceLocator)
{
$config = $serviceLocator->get('Config');
$om = $serviceLocator->get('om');

    $abonnementMapper = $om->getRepository('Membre\Entity\MembreAbonnement');

    $instance = new AbonnementService();
    $instance->setAbonnementMapper($abonnementMapper);                

    return $instance;
}

Can you tell me how to do such a thing with TypeDi ?
David

Interface Injection - not working

Hey,

im having trouble to get Interface Injection working, here is an example:

import {Token, Container, Service, Inject} from 'typedi'

declare interface IFoo {
  foo(): void
}

const FooImpl = new Token<IFoo>()

@Service(FooImpl)
class Foo implements IFoo {
  foo (): void {
    return
  }
}

@Service()
class Bar {

  @Inject(FooImpl)  //throws "Argument of type 'Token<FooImpl>' is not assignable to parameter of type 'string'."
  private foo: IFoo
}

console.log(Container.get<Bar>(Bar))

here is my tsconfig :

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es6",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "outDir": "dist",
    "inlineSourceMap": true,
    "declaration": true,
    "lib": ["es6"],
    "removeComments": true,
    "preserveConstEnums": true,
    "moduleResolution": "node",
    "types": [
      "node",
      "reflect-metadata"
    ]
  },
  "exclude": [
  ],
  "typeRoots": [
    "node_modules/@types"
  ],
  "include": [
    "src/**/*.ts",
    "tests/**/*.ts",
    "node_modules/typedi/**/*.ts"
  ]
}

where is my error?

Scoped container creates new instance of service every time

I've code:

@Service()
class Car {
    public serial = Math.random();
}

console.log(
    Container.of({}).get(Car).serial === Container.of({}).get(Car).serial,
); // false given

But I expect true.

I know, there is global service option, but I don't want to have one instance across all containers. I want to have one instance per every scoped container.

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.