1 | # TypeDI
|
2 |
|
3 | [![Build Status](https://travis-ci.org/typestack/typedi.svg?branch=master)](https://travis-ci.org/typestack/typedi)
|
4 | [![npm version](https://badge.fury.io/js/typedi.svg)](https://badge.fury.io/js/typedi)
|
5 | [![Dependency Status](https://david-dm.org/typestack/typedi.svg)](https://david-dm.org/typestack/typedi)
|
6 | [![Join the chat at https://gitter.im/typestack/typedi](https://badges.gitter.im/typestack/typedi.svg)](https://gitter.im/typestack/typedi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
7 |
|
8 | TypeDI is a [dependency injection](https://en.wikipedia.org/wiki/Dependency_injection) tool for JavaScript and TypeScript.
|
9 | Using TypeDI you can build well-structured and easily tested applications.
|
10 |
|
11 | * [Usage with JavaScript](#usage-with-javascript)
|
12 | * [Usage with TypeScript](#usage-with-typescript)
|
13 |
|
14 | ## Usage with JavaScript
|
15 |
|
16 | Install the module:
|
17 |
|
18 | `npm install typedi --save`
|
19 |
|
20 | Now you can use TypeDI.
|
21 | The most simple usage example is:
|
22 |
|
23 | ```javascript
|
24 | class SomeClass {
|
25 |
|
26 | someMethod() {
|
27 | }
|
28 |
|
29 | }
|
30 |
|
31 | var Container = require("typedi").Container;
|
32 | var someClass = Container.get(SomeClass);
|
33 | someClass.someMethod();
|
34 | ```
|
35 |
|
36 | Then you can call `Container.get(SomeClass)` from anywhere in your application
|
37 | and you'll always have the same instance of `SomeClass`.
|
38 |
|
39 | In your class's constructor you always recieve as a last argument a container which you can use to get other dependencies.
|
40 |
|
41 | ```javascript
|
42 | class BeanFactory {
|
43 | create() {
|
44 | }
|
45 | }
|
46 |
|
47 | class SugarFactory {
|
48 | create() {
|
49 | }
|
50 | }
|
51 |
|
52 | class WaterFactory {
|
53 | create() {
|
54 | }
|
55 | }
|
56 |
|
57 | class CoffeeMaker {
|
58 |
|
59 | constructor(container) {
|
60 | this.beanFactory = container.get(BeanFactory);
|
61 | this.sugarFactory = container.get(SugarFactory);
|
62 | this.waterFactory = container.get(WaterFactory);
|
63 | }
|
64 |
|
65 | make() {
|
66 | this.beanFactory.create();
|
67 | this.sugarFactory.create();
|
68 | this.waterFactory.create();
|
69 | }
|
70 |
|
71 | }
|
72 |
|
73 | var Container = require("typedi").Container;
|
74 | var coffeeMaker = Container.get(CoffeeMaker);
|
75 | coffeeMaker.make();
|
76 | ```
|
77 |
|
78 | With TypeDI you can use a named services. Example:
|
79 |
|
80 | ```javascript
|
81 | var Container = require("typedi").Container;
|
82 |
|
83 | class BeanFactory implements Factory {
|
84 | create() {
|
85 | }
|
86 | }
|
87 |
|
88 | class SugarFactory implements Factory {
|
89 | create() {
|
90 | }
|
91 | }
|
92 |
|
93 | class WaterFactory implements Factory {
|
94 | create() {
|
95 | }
|
96 | }
|
97 |
|
98 | class CoffeeMaker {
|
99 |
|
100 | beanFactory: Factory;
|
101 | sugarFactory: Factory;
|
102 | waterFactory: Factory;
|
103 |
|
104 | constructor(container) {
|
105 | this.beanFactory = container.get("bean.factory");
|
106 | this.sugarFactory = container.get("sugar.factory");
|
107 | this.waterFactory = container.get("water.factory");
|
108 | }
|
109 |
|
110 | make() {
|
111 | this.beanFactory.create();
|
112 | this.sugarFactory.create();
|
113 | this.waterFactory.create();
|
114 | }
|
115 |
|
116 | }
|
117 |
|
118 | Container.set("bean.factory", new BeanFactory(Container));
|
119 | Container.set("sugar.factory", new SugarFactory(Container));
|
120 | Container.set("water.factory", new WaterFactory(Container));
|
121 | Container.set("coffee.maker", new CoffeeMaker(Container));
|
122 |
|
123 | var coffeeMaker = Container.get("coffee.maker");
|
124 | coffeeMaker.make();
|
125 | ```
|
126 |
|
127 | This feature especially useful if you want to store (and inject later on) some settings or configuration options.
|
128 | For example:
|
129 |
|
130 | ```javascript
|
131 | var Container = require("typedi").Container;
|
132 |
|
133 | // somewhere in your global app parameters
|
134 | Container.set("authorization-token", "RVT9rVjSVN");
|
135 |
|
136 | class UserRepository {
|
137 |
|
138 | constructor(container) {
|
139 | this.authorizationToken = container.get("authorization-token");
|
140 | }
|
141 |
|
142 | }
|
143 | ```
|
144 |
|
145 | When you write tests you can easily provide your own "fake" dependencies to classes you are testing using `set` method:
|
146 |
|
147 | ```javascript
|
148 | Container.set(CoffeeMaker, new FakeCoffeeMaker());
|
149 |
|
150 | // or for named services
|
151 |
|
152 | Container.set([
|
153 | { id: "bean.factory", value: new FakeBeanFactory() },
|
154 | { id: "sugar.factory", value: new FakeSugarFactory() },
|
155 | { id: "water.factory", value: new FakeWaterFactory() }
|
156 | ]);
|
157 | ```
|
158 |
|
159 | ## Usage with TypeScript
|
160 |
|
161 | 1. Install module:
|
162 |
|
163 | `npm install typedi --save`
|
164 |
|
165 | 2. Install [reflect-metadata](https://www.npmjs.com/package/reflect-metadata) package:
|
166 |
|
167 | `npm install reflect-metadata --save`
|
168 |
|
169 | and import it somewhere in the global place of your app before any service declaration or import (for example in `app.ts`):
|
170 |
|
171 | `import "reflect-metadata";`
|
172 |
|
173 | 3. You may need to install node typings:
|
174 |
|
175 | `npm install @types/node --save`
|
176 |
|
177 |
|
178 | 4. Enabled following settings in `tsconfig.json`:
|
179 |
|
180 | ```json
|
181 | "emitDecoratorMetadata": true,
|
182 | "experimentalDecorators": true,
|
183 | ```
|
184 |
|
185 | Now you can use TypeDI.
|
186 | The most simple usage example is:
|
187 |
|
188 | ```javascript
|
189 | import "reflect-metadata";
|
190 | import {Service, Container} from "typedi";
|
191 |
|
192 | @Service()
|
193 | class SomeClass {
|
194 |
|
195 | someMethod() {
|
196 | }
|
197 |
|
198 | }
|
199 |
|
200 | let someClass = Container.get(SomeClass);
|
201 | someClass.someMethod();
|
202 | ```
|
203 |
|
204 | Then you can call `Container.get(SomeClass)` from anywhere in your application
|
205 | and you'll always have the same instance of `SomeClass`.
|
206 |
|
207 | You can use **property injection** and inject services into your class using `@Inject` decorator:
|
208 |
|
209 | ```javascript
|
210 | import {Container, Inject, Service} from "typedi";
|
211 |
|
212 | @Service()
|
213 | class BeanFactory {
|
214 | create() {
|
215 | }
|
216 | }
|
217 |
|
218 | @Service()
|
219 | class SugarFactory {
|
220 | create() {
|
221 | }
|
222 | }
|
223 |
|
224 | @Service()
|
225 | class WaterFactory {
|
226 | create() {
|
227 | }
|
228 | }
|
229 |
|
230 | @Service()
|
231 | class CoffeeMaker {
|
232 |
|
233 | @Inject()
|
234 | beanFactory: BeanFactory;
|
235 |
|
236 | @Inject()
|
237 | sugarFactory: SugarFactory;
|
238 |
|
239 | @Inject()
|
240 | waterFactory: WaterFactory;
|
241 |
|
242 | make() {
|
243 | this.beanFactory.create();
|
244 | this.sugarFactory.create();
|
245 | this.waterFactory.create();
|
246 | }
|
247 |
|
248 | }
|
249 |
|
250 | let coffeeMaker = Container.get(CoffeeMaker);
|
251 | coffeeMaker.make();
|
252 | ```
|
253 |
|
254 | You can also use a constructor injection:
|
255 |
|
256 | ```javascript
|
257 | import {Container, Service} from "typedi";
|
258 |
|
259 | @Service()
|
260 | class BeanFactory {
|
261 | create() {
|
262 | }
|
263 | }
|
264 |
|
265 | @Service()
|
266 | class SugarFactory {
|
267 | create() {
|
268 | }
|
269 | }
|
270 |
|
271 | @Service()
|
272 | class WaterFactory {
|
273 | create() {
|
274 | }
|
275 | }
|
276 |
|
277 | @Service()
|
278 | class CoffeeMaker {
|
279 |
|
280 | constructor(private beanFactory: BeanFactory,
|
281 | private sugarFactory: SugarFactory,
|
282 | private waterFactory: WaterFactory) {}
|
283 |
|
284 | make() {
|
285 | this.beanFactory.create();
|
286 | this.sugarFactory.create();
|
287 | this.waterFactory.create();
|
288 | }
|
289 |
|
290 | }
|
291 |
|
292 | let coffeeMaker = Container.get(CoffeeMaker);
|
293 | coffeeMaker.make();
|
294 | ```
|
295 |
|
296 | With TypeDI you can use a named services. Example:
|
297 |
|
298 | ```javascript
|
299 | import {Container, Service, Inject} from "typedi";
|
300 |
|
301 | interface Factory {
|
302 | create(): void;
|
303 | }
|
304 |
|
305 | @Service("bean.factory")
|
306 | class BeanFactory implements Factory {
|
307 | create() {
|
308 | }
|
309 | }
|
310 |
|
311 | @Service("sugar.factory")
|
312 | class SugarFactory implements Factory {
|
313 | create() {
|
314 | }
|
315 | }
|
316 |
|
317 | @Service("water.factory")
|
318 | class WaterFactory implements Factory {
|
319 | create() {
|
320 | }
|
321 | }
|
322 |
|
323 | @Service("coffee.maker")
|
324 | class CoffeeMaker {
|
325 |
|
326 | beanFactory: Factory;
|
327 | sugarFactory: Factory;
|
328 |
|
329 | @Inject("water.factory")
|
330 | waterFactory: Factory;
|
331 |
|
332 | constructor(@Inject("bean.factory") beanFactory: BeanFactory,
|
333 | @Inject("sugar.factory") sugarFactory: SugarFactory) {
|
334 | this.beanFactory = beanFactory;
|
335 | this.sugarFactory = sugarFactory;
|
336 | }
|
337 |
|
338 | make() {
|
339 | this.beanFactory.create();
|
340 | this.sugarFactory.create();
|
341 | this.waterFactory.create();
|
342 | }
|
343 |
|
344 | }
|
345 |
|
346 | let coffeeMaker = Container.get<CoffeeMaker>("coffee.maker");
|
347 | coffeeMaker.make();
|
348 | ```
|
349 |
|
350 | This feature especially useful if you want to store (and inject later on) some settings or configuration options.
|
351 | For example:
|
352 |
|
353 | ```javascript
|
354 | import {Container, Service, Inject} from "typedi";
|
355 |
|
356 | // somewhere in your global app parameters
|
357 | Container.set("authorization-token", "RVT9rVjSVN");
|
358 |
|
359 | @Service()
|
360 | class UserRepository {
|
361 |
|
362 | @Inject("authorization-token")
|
363 | authorizationToken: string;
|
364 |
|
365 | }
|
366 | ```
|
367 |
|
368 | When you write tests you can easily provide your own "fake" dependencies to classes you are testing using `set` method:
|
369 | `provide` methods of the container:
|
370 |
|
371 | ```javascript
|
372 | Container.set(CoffeeMaker, new FakeCoffeeMaker());
|
373 |
|
374 | // or for named services
|
375 |
|
376 | Container.set([
|
377 | { id: "bean.factory", value: new FakeBeanFactory() },
|
378 | { id: "sugar.factory", value: new FakeSugarFactory() },
|
379 | { id: "water.factory", value: new FakeWaterFactory() }
|
380 | ]);
|
381 | ```
|
382 |
|
383 | ## TypeScript Advanced Usage Examples
|
384 |
|
385 | * [Services with token name](#services-with-token-name)
|
386 | * [Using factory function to create service](#using-factory-function-to-create-service)
|
387 | * [Using factory class to create service](#using-factory-class-to-create-service)
|
388 | * [Problem with circular references](#problem-with-circular-references)
|
389 | * [Inherited injections](#inherited-injections)
|
390 | * [Custom decorators](#custom-decorators)
|
391 | * [Using service groups](#using-service-groups)
|
392 | * [Using multiple containers and scoped containers](#using-multiple-containers-and-scoped-containers)
|
393 | * [Remove registered services or reset container state](#remove-registered-services-or-reset-container-state)
|
394 |
|
395 | ### Services with token name
|
396 |
|
397 | You can use a services with a `Token` instead of name or target class.
|
398 | In this case you can use type safe interface-based services.
|
399 |
|
400 | ```javascript
|
401 | import {Container, Service, Inject, Token} from "typedi";
|
402 |
|
403 | export interface Factory {
|
404 | create(): void;
|
405 | }
|
406 |
|
407 | export const FactoryService = new Token<Factory>();
|
408 |
|
409 | @Service(FactoryService)
|
410 | export class BeanFactory implements Factory {
|
411 | create() {
|
412 | }
|
413 | }
|
414 |
|
415 | @Service()
|
416 | export class CoffeeMaker {
|
417 |
|
418 | private factory: Factory;
|
419 |
|
420 | constructor(@Inject(type => FactoryService) factory: Factory) {
|
421 | this.factory = factory;
|
422 | }
|
423 |
|
424 | make() {
|
425 | this.factory.create();
|
426 | }
|
427 |
|
428 | }
|
429 |
|
430 | let coffeeMaker = Container.get(CoffeeMaker);
|
431 | coffeeMaker.make();
|
432 |
|
433 | let factory = Container.get(FactoryService); // factory is instance of Factory
|
434 | factory.create();
|
435 | ```
|
436 |
|
437 | ### Using factory function to create service
|
438 |
|
439 | You can create your services with the container using factory functions.
|
440 |
|
441 | This way, service instance will be created by calling your factory function instead of
|
442 | instantiating a class directly.
|
443 |
|
444 | ```javascript
|
445 | import {Container, Service} from "typedi";
|
446 |
|
447 | function createCar() {
|
448 | return new Car("V8");
|
449 | }
|
450 |
|
451 | @Service({ factory: createCar })
|
452 | class Car {
|
453 | constructor (public engineType: string) {
|
454 | }
|
455 | }
|
456 |
|
457 | // Getting service from the container.
|
458 | // Service will be created by calling the specified factory function.
|
459 | const car = Container.get(Car);
|
460 |
|
461 | console.log(car.engineType); // > "V8"
|
462 | ```
|
463 |
|
464 | ### Using factory class to create service
|
465 |
|
466 | You can also create your services using factory classes.
|
467 |
|
468 | This way, service instance will be created by calling given factory service's method factory instead of
|
469 | instantiating a class directly.
|
470 |
|
471 | ```javascript
|
472 | import {Container, Service} from "typedi";
|
473 |
|
474 | @Service()
|
475 | class CarFactory {
|
476 |
|
477 | constructor(public logger: LoggerService) {
|
478 | }
|
479 |
|
480 | create() {
|
481 | return new Car("BMW", this.logger);
|
482 | }
|
483 |
|
484 | }
|
485 |
|
486 | @Service({ factory: [CarFactory, "create"] })
|
487 | class Car {
|
488 | constructor(public model: string, public logger: LoggerInterface) {
|
489 | }
|
490 | }
|
491 | ```
|
492 |
|
493 | ### Problem with circular references
|
494 |
|
495 | There is a known issue in language that it can't handle circular references. For example:
|
496 |
|
497 | ```javascript
|
498 | // Car.ts
|
499 | @Service()
|
500 | export class Car {
|
501 | @Inject()
|
502 | engine: Engine;
|
503 | }
|
504 |
|
505 | // Engine.ts
|
506 | @Service()
|
507 | export class Engine {
|
508 | @Inject()
|
509 | car: Car;
|
510 | }
|
511 | ```
|
512 |
|
513 | This code will not work, because Engine has a reference to Car, and Car has a reference to Engine.
|
514 | One of them will be undefined and it cause errors. To fix them you need to specify a type in a function this way:
|
515 |
|
516 | ```javascript
|
517 | // Car.ts
|
518 | @Service()
|
519 | export class Car {
|
520 | @Inject(type => Engine)
|
521 | engine: Engine;
|
522 | }
|
523 |
|
524 | // Engine.ts
|
525 | @Service()
|
526 | export class Engine {
|
527 | @Inject(type => Car)
|
528 | car: Car;
|
529 | }
|
530 | ```
|
531 |
|
532 | And that's all. Same for constructor injections.
|
533 |
|
534 | ### Inherited injections
|
535 |
|
536 | Inherited injections are supported as well. In order to use them you must mark inherited class as a @Service.
|
537 | For example:
|
538 |
|
539 | ```javascript
|
540 | // Car.ts
|
541 | @Service()
|
542 | export abstract class Car {
|
543 |
|
544 | @Inject()
|
545 | engine: Engine;
|
546 |
|
547 | }
|
548 |
|
549 | // Engine.ts
|
550 | @Service()
|
551 | export class Bus extends Car {
|
552 |
|
553 | // you can call this.engine in this class
|
554 | }
|
555 | ```
|
556 |
|
557 | ### Custom decorators
|
558 |
|
559 | You can create your own decorators which will inject your given values for your service dependencies.
|
560 | For example:
|
561 |
|
562 | ```javascript
|
563 | // Logger.ts
|
564 | export function Logger() {
|
565 | return function(object: Object, propertyName: string, index?: number) {
|
566 | const logger = new ConsoleLogger();
|
567 | Container.registerHandler({ object, propertyName, index, value: containerInstance => logger });
|
568 | };
|
569 | }
|
570 |
|
571 | // LoggerInterface.ts
|
572 | export interface LoggerInterface {
|
573 |
|
574 | log(message: string): void;
|
575 |
|
576 | }
|
577 |
|
578 | // ConsoleLogger.ts
|
579 | import {LoggerInterface} from "./LoggerInterface";
|
580 |
|
581 | export class ConsoleLogger implements LoggerInterface {
|
582 |
|
583 | log(message: string) {
|
584 | console.log(message);
|
585 | }
|
586 |
|
587 | }
|
588 |
|
589 | // UserRepository.ts
|
590 | @Service()
|
591 | export class UserRepository {
|
592 |
|
593 | constructor(@Logger() private logger: LoggerInterface) {
|
594 | }
|
595 |
|
596 | save(user: User) {
|
597 | this.logger.log(`user ${user.firstName} ${user.secondName} has been saved.`);
|
598 | }
|
599 |
|
600 | }
|
601 | ```
|
602 |
|
603 | ### Using service groups
|
604 |
|
605 | You can group multiple services into single group tagged with service id or token.
|
606 | For example:
|
607 |
|
608 | ```javascript
|
609 | // Factory.ts
|
610 | export interface Factory {
|
611 | create(): any;
|
612 | }
|
613 |
|
614 | // FactoryToken.ts
|
615 | export const FactoryToken = new Token<Factory>("factories");
|
616 |
|
617 | // BeanFactory.ts
|
618 | @Service({ id: FactoryToken, multiple: true })
|
619 | export class BeanFactory implements Factory {
|
620 |
|
621 | create() {
|
622 | console.log("bean created");
|
623 | }
|
624 |
|
625 | }
|
626 |
|
627 | // SugarFactory.ts
|
628 | @Service({ id: FactoryToken, multiple: true })
|
629 | export class SugarFactory implements Factory {
|
630 |
|
631 | create() {
|
632 | console.log("sugar created");
|
633 | }
|
634 |
|
635 | }
|
636 |
|
637 | // WaterFactory.ts
|
638 | @Service({ id: FactoryToken, multiple: true })
|
639 | export class WaterFactory implements Factory {
|
640 |
|
641 | create() {
|
642 | console.log("water created");
|
643 | }
|
644 |
|
645 | }
|
646 |
|
647 | // app.ts
|
648 | // now you can get all factories in a single array
|
649 | Container.import([
|
650 | BeanFactory,
|
651 | SugarFactory,
|
652 | WaterFactory,
|
653 | ]);
|
654 | const factories = Container.getMany(FactoryToken); // factories is Factory[]
|
655 | factories.forEach(factory => factory.create());
|
656 | ```
|
657 |
|
658 | ### Using multiple containers and scoped containers
|
659 |
|
660 | By default all services are stored in the global service container,
|
661 | and this global service container holds all unique instances of each service you have.
|
662 |
|
663 | If you want your services to behave and store data inside differently,
|
664 | based on some user context (http request for example) -
|
665 | you can use different containers for different contexts.
|
666 | For example:
|
667 |
|
668 | ```javascript
|
669 | // QuestionController.ts
|
670 | @Service()
|
671 | export class QuestionController {
|
672 |
|
673 | constructor(protected questionRepository: QuestionRepository) {
|
674 | }
|
675 |
|
676 | save() {
|
677 | this.questionRepository.save();
|
678 | }
|
679 | }
|
680 |
|
681 | // QuestionRepository.ts
|
682 | @Service()
|
683 | export class QuestionRepository {
|
684 |
|
685 | save() {
|
686 | }
|
687 |
|
688 | }
|
689 |
|
690 | // app.ts
|
691 | const request1 = { param: "question1" };
|
692 | const controller1 = Container.of(request1).get(QuestionController);
|
693 | controller1.save("Timber");
|
694 | Container.reset(request1);
|
695 |
|
696 | const request2 = { param: "question2" };
|
697 | const controller2 = Container.of(request2).get(QuestionController);
|
698 | controller2.save("");
|
699 | Container.reset(request2);
|
700 | ```
|
701 |
|
702 | In this example `controller1` and `controller2` are completely different instances,
|
703 | and `QuestionRepository` used in those controllers are different instances as well.
|
704 |
|
705 | `Container.reset` removes container with the given context identifier.
|
706 | If you want your services to be completely global and not be container-specific,
|
707 | you can mark them as global:
|
708 |
|
709 | ```javascript
|
710 | @Service({ global: true })
|
711 | export class QuestionUtils {
|
712 |
|
713 | }
|
714 | ```
|
715 |
|
716 | And this global service will be the same instance across all containers.
|
717 |
|
718 | ### Remove registered services or reset container state
|
719 |
|
720 | If you need to remove registered service from container simply use `Container.remove(...)` method.
|
721 | Also you can completely reset the container by calling `Container.reset()` method.
|
722 | This will effectively remove all registered services from the container.
|
723 |
|
724 | ## Troubleshooting
|
725 |
|
726 | ### Use TypeDI with routing-controllers and/or TypeORM
|
727 |
|
728 | In order to use typedi with routing-controllers and/or typeorm, it's **necessary** to tell these libs to use the typedi container.
|
729 | Otherwise you may face [this kind of issue](https://github.com/pleerock/typedi/issues/4).
|
730 |
|
731 | ```Typescript
|
732 | import {useContainer as routingUseContainer} from "routing-controllers";
|
733 | import {useContainer as ormUseContainer} from "typeorm";
|
734 | import {Container} from "typedi";
|
735 |
|
736 | routingUseContainer(Container);
|
737 | ormUseContainer(Container);
|
738 | ```
|
739 |
|
740 | ## Samples
|
741 |
|
742 | Take a look on samples in [./sample](https://github.com/pleerock/typedi/tree/master/sample) for examples of usage.
|