Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

get/inject an array of service instances #24

Closed
ssljivic opened this issue May 2, 2017 · 13 comments
Closed

get/inject an array of service instances #24

ssljivic opened this issue May 2, 2017 · 13 comments
Labels
type: feature Issues related to new features.

Comments

@ssljivic
Copy link

ssljivic commented May 2, 2017

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.

@pleerock
Copy link
Contributor

pleerock commented May 3, 2017

Can you provide an example of functionality you need?

@ssljivic
Copy link
Author

ssljivic commented May 3, 2017

For example:

@service('handler')
class HandlerX implements Handler {...}

@service('handler')
class HandlerY implements Handler {...}

...

@Inject('handler')
handlers: Handler[];

@pleerock
Copy link
Contributor

pleerock commented May 3, 2017

no there is no such way, because each token holds single unique instance of the object

@ssljivic
Copy link
Author

ssljivic commented May 3, 2017

Any plans to support such a feature in the future? Most if not all DI frameworks support this kind of injection.

@pleerock
Copy link
Contributor

probably this can be implemented in the future. Need to start with a design proposal, simply @service('handler') wont work. We need to specify somehow that this service will work as an array of instances

@alexproca
Copy link

For starters what about

import { Container } from "typedi";

@service('handler')
class HandlerX implements Handler {...}

@service('handler')
class HandlerY implements Handler {...}

...

let allHandlera = Container.getAll(Handler)

And afterwards we can find a suitable way to use decorators for this functionality.

@steven166
Copy link

In my opinion, you should be able to define the scope of the beans/tokens. For example, Spring has a couple of different scopes for beans, where the following 3 are the most relevant:

  • singleton (default): Scopes a single bean definition to a single object instance per Spring IoC container. (like typedi already does)
  • prototype: Scopes a single bean definition to any number of object instances. (like requested in this issue)
  • request: Scopes a single bean definition to the lifecycle of a single HTTP request; that is each and every HTTP request will have its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext. (related to this feature request: Question: one controller instance per request routing-controllers#174)

Here are examples how prototype and request scopes could be implemented with typeid (btw. I also likes @alexproca idea):

Prototype scope

import { Service, Scope } from "typedi"

@Service({ scope: Scope.Prototype })
class HandlerX implements Handler {} 

@Service({ scope: Scope.Prototype })
class HandlerY implements Handler {}
...
@Inject()
handlers: Handler[];

Request scope
Define tokens/beans

import { Service, Scope } from "typedi"

// Each request has its own TodoController instance
@Service({ scope: Scope.Request })
class TodoController {
  @Inject()
  todoService: TodoService;
} 

// Each request has its own TodoService instance
@Service({ scope: Scope.Request })
class TodoService {
  @Inject()
  todoRepo: TodoRepository;
} 

// All requests use the same repository instance
@Service({ scope: Scope.Singleton })
class TodoRepository {}

Initialize a new request scope

import { RequestContainer } from "typedi"

// create RequestContainer with a request context object, it inherit all tokens/beans from Container
let requestContainer = new RequestContainer( contextObject );
// get an instance for the current request
let todoController = requestScope.get( TodoController );

@tonivj5
Copy link
Contributor

tonivj5 commented Aug 8, 2017

I think it could be easier adding a multi property like angular's DI: https://angular.io/api/core/FactoryProvider#example-1

@pleerock
Copy link
Contributor

what @steven166 says does make sense, we'll need to implement this functionality

@phryneas
Copy link

phryneas commented Oct 1, 2017

I'm also looking for something like this. Maybe it could be something like "tagged services"?

import { Container, Token } from "typedi";

export const HandlerTag= new Token<Handler>();

@Service
@Reflect.metadata("tag", HandlerTag)
class HandlerX implements Handler {...}

@Service
@Reflect.metadata("tag", HandlerTag)
class HandlerY implements Handler {...}

...

let allHandlers = Container.getTaggedServices(HandlerTag)

@pleerock
Copy link
Contributor

I've implemented this feature and released it in 0.6.0:

Using service groups

You can group multiple services into single group tagged with service id or token.
For example:

// Factory.ts
export interface Factory {
    create(): any;
}

// FactoryToken.ts
export const FactoryToken = new Token<Factory>("factories");

// BeanFactory.ts
@Service({ id: FactoryToken, multiple: true })
export class BeanFactory implements Factory {

    create() {
        console.log("bean created");
    }

}

// SugarFactory.ts
@Service({ id: FactoryToken, multiple: true })
export class SugarFactory implements Factory {

    create() {
        console.log("sugar created");
    }

}

// WaterFactory.ts
@Service({ id: FactoryToken, multiple: true })
export class WaterFactory implements Factory {

    create() {
        console.log("water created");
    }

}

// app.ts
// now you can get all factories in a single array 
const factories = Container.getMany(FactoryToken); // factories is Factory[]
factories.forEach(factory => factory.create());

Using multiple containers and scoped containers

By default all services are stored in the global service container,
and this global service container holds all unique instances of each service you have.

If you want your services to behave and store data inside differently,
based on some user context (http request for example) -
you can use different containers for different contexts.
For example:

// QuestionController.ts
@Service()
export class QuestionController {

    constructor(protected questionRepository: QuestionRepository) {
    }

    save() {
        this.questionRepository.save();
    }
}

// QuestionRepository.ts
@Service()
export class QuestionRepository {

    save() {
    }

}

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

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

In this example controller1 and controller2 are completely different instances,
and QuestionRepository used in those controllers are different instances as well.

Container.reset removes container with the given context identifier.
If you want your services to be completely global and not be container-specific,
you can mark them as global:

@Service({ global: true })
export class QuestionUtils {
  
}

And this global service will be the same instance across all containers.

@fjlucas
Copy link

fjlucas commented Jan 4, 2018

IMHO service groups has a problem. When you want to have several implementations of the same interface, most of the cases what you want at the end is to inject all them into an array in another class without having to know concrete implementations of that interface.

In this approach, you have to import all possible implemtantions what breaks the concept of DI and IOC. Any idea about how handling this scenario? Thanks!

@github-actions
Copy link

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jul 31, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
type: feature Issues related to new features.
Development

No branches or pull requests

7 participants