UNPKG

17.4 kBMarkdownView Raw
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
8TypeDI is a [dependency injection](https://en.wikipedia.org/wiki/Dependency_injection) tool for JavaScript and TypeScript.
9Using 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
16Install the module:
17
18`npm install typedi --save`
19
20Now you can use TypeDI.
21The most simple usage example is:
22
23```javascript
24class SomeClass {
25
26 someMethod() {
27 }
28
29}
30
31var Container = require("typedi").Container;
32var someClass = Container.get(SomeClass);
33someClass.someMethod();
34```
35
36Then you can call `Container.get(SomeClass)` from anywhere in your application
37 and you'll always have the same instance of `SomeClass`.
38
39In your class's constructor you always recieve as a last argument a container which you can use to get other dependencies.
40
41```javascript
42class BeanFactory {
43 create() {
44 }
45}
46
47class SugarFactory {
48 create() {
49 }
50}
51
52class WaterFactory {
53 create() {
54 }
55}
56
57class 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
73var Container = require("typedi").Container;
74var coffeeMaker = Container.get(CoffeeMaker);
75coffeeMaker.make();
76```
77
78With TypeDI you can use a named services. Example:
79
80```javascript
81var Container = require("typedi").Container;
82
83class BeanFactory implements Factory {
84 create() {
85 }
86}
87
88class SugarFactory implements Factory {
89 create() {
90 }
91}
92
93class WaterFactory implements Factory {
94 create() {
95 }
96}
97
98class 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
118Container.set("bean.factory", new BeanFactory(Container));
119Container.set("sugar.factory", new SugarFactory(Container));
120Container.set("water.factory", new WaterFactory(Container));
121Container.set("coffee.maker", new CoffeeMaker(Container));
122
123var coffeeMaker = Container.get("coffee.maker");
124coffeeMaker.make();
125```
126
127This feature especially useful if you want to store (and inject later on) some settings or configuration options.
128For example:
129
130```javascript
131var Container = require("typedi").Container;
132
133// somewhere in your global app parameters
134Container.set("authorization-token", "RVT9rVjSVN");
135
136class UserRepository {
137
138 constructor(container) {
139 this.authorizationToken = container.get("authorization-token");
140 }
141
142}
143```
144
145When you write tests you can easily provide your own "fake" dependencies to classes you are testing using `set` method:
146
147```javascript
148Container.set(CoffeeMaker, new FakeCoffeeMaker());
149
150// or for named services
151
152Container.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
1611. Install module:
162
163 `npm install typedi --save`
164
1652. 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
1733. You may need to install node typings:
174
175 `npm install @types/node --save`
176
177
1784. Enabled following settings in `tsconfig.json`:
179
180```json
181"emitDecoratorMetadata": true,
182"experimentalDecorators": true,
183```
184
185Now you can use TypeDI.
186The most simple usage example is:
187
188```javascript
189import "reflect-metadata";
190import {Service, Container} from "typedi";
191
192@Service()
193class SomeClass {
194
195 someMethod() {
196 }
197
198}
199
200let someClass = Container.get(SomeClass);
201someClass.someMethod();
202```
203
204Then you can call `Container.get(SomeClass)` from anywhere in your application
205 and you'll always have the same instance of `SomeClass`.
206
207You can use **property injection** and inject services into your class using `@Inject` decorator:
208
209```javascript
210import {Container, Inject, Service} from "typedi";
211
212@Service()
213class BeanFactory {
214 create() {
215 }
216}
217
218@Service()
219class SugarFactory {
220 create() {
221 }
222}
223
224@Service()
225class WaterFactory {
226 create() {
227 }
228}
229
230@Service()
231class 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
250let coffeeMaker = Container.get(CoffeeMaker);
251coffeeMaker.make();
252```
253
254You can also use a constructor injection:
255
256```javascript
257import {Container, Service} from "typedi";
258
259@Service()
260class BeanFactory {
261 create() {
262 }
263}
264
265@Service()
266class SugarFactory {
267 create() {
268 }
269}
270
271@Service()
272class WaterFactory {
273 create() {
274 }
275}
276
277@Service()
278class 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
292let coffeeMaker = Container.get(CoffeeMaker);
293coffeeMaker.make();
294```
295
296With TypeDI you can use a named services. Example:
297
298```javascript
299import {Container, Service, Inject} from "typedi";
300
301interface Factory {
302 create(): void;
303}
304
305@Service("bean.factory")
306class BeanFactory implements Factory {
307 create() {
308 }
309}
310
311@Service("sugar.factory")
312class SugarFactory implements Factory {
313 create() {
314 }
315}
316
317@Service("water.factory")
318class WaterFactory implements Factory {
319 create() {
320 }
321}
322
323@Service("coffee.maker")
324class 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
346let coffeeMaker = Container.get<CoffeeMaker>("coffee.maker");
347coffeeMaker.make();
348```
349
350This feature especially useful if you want to store (and inject later on) some settings or configuration options.
351For example:
352
353```javascript
354import {Container, Service, Inject} from "typedi";
355
356// somewhere in your global app parameters
357Container.set("authorization-token", "RVT9rVjSVN");
358
359@Service()
360class UserRepository {
361
362 @Inject("authorization-token")
363 authorizationToken: string;
364
365}
366```
367
368When 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
372Container.set(CoffeeMaker, new FakeCoffeeMaker());
373
374// or for named services
375
376Container.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
397You can use a services with a `Token` instead of name or target class.
398In this case you can use type safe interface-based services.
399
400```javascript
401import {Container, Service, Inject, Token} from "typedi";
402
403export interface Factory {
404 create(): void;
405}
406
407export const FactoryService = new Token<Factory>();
408
409@Service(FactoryService)
410export class BeanFactory implements Factory {
411 create() {
412 }
413}
414
415@Service()
416export 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
430let coffeeMaker = Container.get(CoffeeMaker);
431coffeeMaker.make();
432
433let factory = Container.get(FactoryService); // factory is instance of Factory
434factory.create();
435```
436
437### Using factory function to create service
438
439You can create your services with the container using factory functions.
440
441This way, service instance will be created by calling your factory function instead of
442instantiating a class directly.
443
444```javascript
445import {Container, Service} from "typedi";
446
447function createCar() {
448 return new Car("V8");
449}
450
451@Service({ factory: createCar })
452class 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.
459const car = Container.get(Car);
460
461console.log(car.engineType); // > "V8"
462```
463
464### Using factory class to create service
465
466You can also create your services using factory classes.
467
468This way, service instance will be created by calling given factory service's method factory instead of
469instantiating a class directly.
470
471```javascript
472import {Container, Service} from "typedi";
473
474@Service()
475class 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"] })
487class Car {
488 constructor(public model: string, public logger: LoggerInterface) {
489 }
490}
491```
492
493### Problem with circular references
494
495There is a known issue in language that it can't handle circular references. For example:
496
497```javascript
498// Car.ts
499@Service()
500export class Car {
501 @Inject()
502 engine: Engine;
503}
504
505// Engine.ts
506@Service()
507export class Engine {
508 @Inject()
509 car: Car;
510}
511```
512
513This code will not work, because Engine has a reference to Car, and Car has a reference to Engine.
514One 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()
519export class Car {
520 @Inject(type => Engine)
521 engine: Engine;
522}
523
524// Engine.ts
525@Service()
526export class Engine {
527 @Inject(type => Car)
528 car: Car;
529}
530```
531
532And that's all. Same for constructor injections.
533
534### Inherited injections
535
536Inherited injections are supported as well. In order to use them you must mark inherited class as a @Service.
537For example:
538
539```javascript
540// Car.ts
541@Service()
542export abstract class Car {
543
544 @Inject()
545 engine: Engine;
546
547}
548
549// Engine.ts
550@Service()
551export class Bus extends Car {
552
553 // you can call this.engine in this class
554}
555```
556
557### Custom decorators
558
559You can create your own decorators which will inject your given values for your service dependencies.
560For example:
561
562```javascript
563// Logger.ts
564export 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
572export interface LoggerInterface {
573
574 log(message: string): void;
575
576}
577
578// ConsoleLogger.ts
579import {LoggerInterface} from "./LoggerInterface";
580
581export class ConsoleLogger implements LoggerInterface {
582
583 log(message: string) {
584 console.log(message);
585 }
586
587}
588
589// UserRepository.ts
590@Service()
591export 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
605You can group multiple services into single group tagged with service id or token.
606For example:
607
608```javascript
609// Factory.ts
610export interface Factory {
611 create(): any;
612}
613
614// FactoryToken.ts
615export const FactoryToken = new Token<Factory>("factories");
616
617// BeanFactory.ts
618@Service({ id: FactoryToken, multiple: true })
619export 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 })
629export 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 })
639export 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
649Container.import([
650 BeanFactory,
651 SugarFactory,
652 WaterFactory,
653]);
654const factories = Container.getMany(FactoryToken); // factories is Factory[]
655factories.forEach(factory => factory.create());
656```
657
658### Using multiple containers and scoped containers
659
660By default all services are stored in the global service container,
661and this global service container holds all unique instances of each service you have.
662
663If you want your services to behave and store data inside differently,
664based on some user context (http request for example) -
665you can use different containers for different contexts.
666For example:
667
668```javascript
669// QuestionController.ts
670@Service()
671export class QuestionController {
672
673 constructor(protected questionRepository: QuestionRepository) {
674 }
675
676 save() {
677 this.questionRepository.save();
678 }
679}
680
681// QuestionRepository.ts
682@Service()
683export class QuestionRepository {
684
685 save() {
686 }
687
688}
689
690// app.ts
691const request1 = { param: "question1" };
692const controller1 = Container.of(request1).get(QuestionController);
693controller1.save("Timber");
694Container.reset(request1);
695
696const request2 = { param: "question2" };
697const controller2 = Container.of(request2).get(QuestionController);
698controller2.save("");
699Container.reset(request2);
700```
701
702In this example `controller1` and `controller2` are completely different instances,
703and `QuestionRepository` used in those controllers are different instances as well.
704
705`Container.reset` removes container with the given context identifier.
706If you want your services to be completely global and not be container-specific,
707you can mark them as global:
708
709```javascript
710@Service({ global: true })
711export class QuestionUtils {
712
713}
714```
715
716And this global service will be the same instance across all containers.
717
718### Remove registered services or reset container state
719
720If you need to remove registered service from container simply use `Container.remove(...)` method.
721Also you can completely reset the container by calling `Container.reset()` method.
722This will effectively remove all registered services from the container.
723
724## Troubleshooting
725
726### Use TypeDI with routing-controllers and/or TypeORM
727
728In order to use typedi with routing-controllers and/or typeorm, it's **necessary** to tell these libs to use the typedi container.
729Otherwise you may face [this kind of issue](https://github.com/pleerock/typedi/issues/4).
730
731```Typescript
732import {useContainer as routingUseContainer} from "routing-controllers";
733import {useContainer as ormUseContainer} from "typeorm";
734import {Container} from "typedi";
735
736routingUseContainer(Container);
737ormUseContainer(Container);
738```
739
740## Samples
741
742Take a look on samples in [./sample](https://github.com/pleerock/typedi/tree/master/sample) for examples of usage.