Skip to content

Commit 5d927cc

Browse files
authored
Merge pull request #18 from pleerock/typedi-factory-fixes-and-refactoring
fixes in factory support + refactoring
2 parents c2095ea + f5f8c00 commit 5d927cc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1252
-653
lines changed

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
.vscode/
2+
.idea/
13
build/
24
node_modules/
3-
typings/
45
npm-debug.log
5-
.idea/

README.md

Lines changed: 163 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# TypeDI
22

3-
Dependency injection tool for Typescript.
3+
Simple yet powerful dependency injection tool for Typescript.
44

55
## Installation
66

@@ -9,17 +9,27 @@ Dependency injection tool for Typescript.
99

1010
`npm install typedi --save`
1111

12-
2. Use [typings](https://github.com/typings/typings) to install all required definition dependencies.
12+
2. You also need to install [reflect-metadata](https://www.npmjs.com/package/reflect-metadata) package.
1313

14-
`typings install`
14+
`npm install reflect-metadata --save`
15+
16+
and import it somewhere in the global place of your app (for example in `app.ts`):
17+
18+
`import "reflect-metadata";`
1519

16-
3. ES6 features are used, so you may want to install [es6-shim](https://github.com/paulmillr/es6-shim) too. You also
17-
need to install [reflect-metadata](https://www.npmjs.com/package/reflect-metadata) package.
20+
3. You may need to install node typings:
1821

19-
`npm install es6-shim --save`
20-
`npm install reflect-metadata --save`
22+
`npm install @types/node --save`
23+
24+
25+
4. Also make sure you are using TypeScript compiler version > **2.1**
26+
and you have enabled following settings in `tsconfig.json`:
2127

22-
if you are building nodejs app, you may want to `require("es6-shim");` and `require("reflect-metadata")` in your app.
28+
```json
29+
"lib": ["es6"],
30+
"emitDecoratorMetadata": true,
31+
"experimentalDecorators": true,
32+
```
2333

2434
## Usage
2535

@@ -42,23 +52,27 @@ someClass.someMethod();
4252
If you want to inject other classes into your service you can do:
4353

4454
```typescript
45-
import {Container, Inject} from "typedi";
55+
import {Container, Inject, Service} from "typedi";
4656

57+
@Service()
4758
class BeanFactory {
4859
create() {
4960
}
5061
}
5162

63+
@Service()
5264
class SugarFactory {
5365
create() {
5466
}
5567
}
5668

69+
@Service()
5770
class WaterFactory {
5871
create() {
5972
}
6073
}
6174

75+
@Service()
6276
class CoffeeMaker {
6377

6478
@Inject()
@@ -87,16 +101,19 @@ If you want to use constructor injection:
87101
```typescript
88102
import {Container, Service} from "typedi";
89103

104+
@Service()
90105
class BeanFactory {
91106
create() {
92107
}
93108
}
94109

110+
@Service()
95111
class SugarFactory {
96112
create() {
97113
}
98114
}
99115

116+
@Service()
100117
class WaterFactory {
101118
create() {
102119
}
@@ -128,10 +145,10 @@ coffeeMaker.make();
128145
```
129146

130147
> note: Your classes may not to have `@Service` decorator to use it with Container, however its recommended to add
131-
`@Service` decorator to all classes you are using with container, especially if you class injects other
132-
services
148+
`@Service` decorator to all classes you are using with container, because without `@Service` decorator applied
149+
constructor injection may not work properly in your classes.
133150

134-
### Extra feature: Injecting third-party dependencies *(experimental)*
151+
### Injecting third-party dependencies *(experimental)*
135152

136153
Also you can inject a modules that you want to `require`:
137154

@@ -141,14 +158,14 @@ import {Container, Service, Require} from "typedi";
141158
@Service()
142159
class CoffeeMaker {
143160

144-
private gulp: any; // you can use type if you have definition for this package
161+
private logger: any; // you can use type if you have definition for this package
145162

146-
constructor(@Require("gulp") gulp: any) {
147-
this.gulp = gulp; // the same if you do this.gulp = require("gulp")
163+
constructor(@Require("logger") logger: any) {
164+
this.logger = logger; // the same if you do this.logger = require("logger")
148165
}
149166

150167
make() {
151-
console.log(this.gulp); // here you get console.logged gulp package =)
168+
console.log(this.logger); // here you get console.logged logger package =)
152169
}
153170
}
154171

@@ -212,24 +229,63 @@ let coffeeMaker = Container.get<CoffeeMaker>("coffee.maker");
212229
coffeeMaker.make();
213230
```
214231

232+
### Services with token name
233+
234+
You can use a services with a `Token` instead of name or target class.
235+
In this case you can use type safe interface-based services.
236+
237+
```typescript
238+
import {Container, Service, Inject, Token} from "typedi";
239+
240+
export interface Factory {
241+
create(): void;
242+
}
243+
244+
export const FactoryService = new Token<Factory>();
245+
246+
@Service(FactoryService)
247+
export class BeanFactory implements Factory {
248+
create() {
249+
}
250+
}
251+
252+
@Service()
253+
export class CoffeeMaker {
254+
255+
private factory: Factory;
256+
257+
constructor(@Inject(FactoryService) factory: Factory) {
258+
this.factory = factory;
259+
}
260+
261+
make() {
262+
this.factory.create();
263+
}
264+
265+
}
266+
267+
let coffeeMaker = Container.get(CoffeeMaker);
268+
coffeeMaker.make();
269+
270+
let factory = Container.get(FactoryService);
271+
factory.create();
272+
```
273+
215274
### Using factory function to create service
216275

217-
You can register your services with the container using factory functions.
276+
You can create your services with the container using factory functions.
218277

219278
This way, service instance will be created by calling your factory function instead of
220279
instantiating a class directly.
221280

222281
```typescript
223282
import {Container, Service} from "typedi";
224283

225-
226-
class CarFactory {
227-
public static createCar(): Car {
228-
return new Car("V8");
229-
}
284+
function createCar() {
285+
return new Car("V8");
230286
}
231287

232-
@Service({ factory: CarFactory.createCar })
288+
@Service({ factory: createCar })
233289
class Car {
234290
constructor (public engineType: string) {
235291
}
@@ -242,6 +298,35 @@ const car = Container.get(Car);
242298
console.log(car.engineType); // > "V8"
243299
```
244300

301+
### Using factory class to create service
302+
303+
You can also create your services using factory classes.
304+
305+
This way, service instance will be created by calling given factory service's method factory instead of
306+
instantiating a class directly.
307+
308+
```typescript
309+
import {Container, Service} from "typedi";
310+
311+
@Service()
312+
class CarFactory {
313+
314+
constructor(public logger: LoggerService) {
315+
}
316+
317+
create() {
318+
return new Car("BMW", this.logger);
319+
}
320+
321+
}
322+
323+
@Service({ factory: [CarFactory, "create"] })
324+
class Car {
325+
constructor(public model: string, public logger: LoggerInterface) {
326+
}
327+
}
328+
```
329+
245330
### Providing values to the container
246331

247332
If you are writing unit tests for you class, you may want to provide fakes to your classes. You can use `set` or
@@ -250,12 +335,12 @@ If you are writing unit tests for you class, you may want to provide fakes to yo
250335
```typescript
251336
Container.set(CoffeeMaker, new FakeCoffeeMaker());
252337

253-
// or alternatively:
338+
// or
254339

255340
Container.provide([
256-
{ name: "bean.factory", type: BeanFactory, value: new FakeBeanFactory() },
257-
{ name: "sugar.factory", type: SugarFactory, value: new FakeSugarFactory() },
258-
{ name: "water.factory", type: WaterFactory, value: new FakeWaterFactory() }
341+
{ id: "bean.factory", value: new FakeBeanFactory() },
342+
{ id: "sugar.factory", value: new FakeSugarFactory() },
343+
{ id: "water.factory", value: new FakeWaterFactory() }
259344
]);
260345
```
261346

@@ -280,7 +365,7 @@ export class Engine {
280365
```
281366

282367
This code will not work, because Engine has a reference to Car, and Car has a reference to Engine.
283-
One of them will be undefined and it will cause an errors. To fix them you need to specify a type in a function like this:
368+
One of them will be undefined and it cause errors. To fix them you need to specify a type in a function this way:
284369

285370
```typescript
286371
// Car.ts
@@ -323,10 +408,57 @@ export class Bus extends Car {
323408
}
324409
```
325410

326-
### Container reset
411+
### Custom decorators
412+
413+
You can create your own decorators which will inject your given values for your service dependencies.
414+
For example:
415+
416+
```typescript
417+
// Logger.ts
418+
export function Logger() {
419+
return function(object: Object, propertyName: string, index?: number) {
420+
const logger = new ConsoleLogger();
421+
Container.registerHandler({ object, propertyName, index, value: () => logger });
422+
};
423+
}
424+
425+
// LoggerInterface.ts
426+
export interface LoggerInterface {
427+
428+
log(message: string): void;
429+
430+
}
431+
432+
// ConsoleLogger.ts
433+
import {LoggerInterface} from "./LoggerInterface";
434+
435+
export class ConsoleLogger implements LoggerInterface {
436+
437+
log(message: string) {
438+
console.log(message);
439+
}
440+
441+
}
442+
443+
// UserRepository.ts
444+
@Service()
445+
export class UserRepository {
446+
447+
constructor(@Logger() private logger: LoggerInterface) {
448+
}
449+
450+
save(user: User) {
451+
this.logger.log(`user ${user.firstName} ${user.secondName} has been saved.`);
452+
}
453+
454+
}
455+
```
456+
457+
### Remove registered services or reset container state
327458

328-
You can reset the container by calling `Container.reset()` method.
329-
This will effectively remove references to all registered artifacts from it, making it pristine (empty).
459+
If you need to remove registered service from container simply use `Container.remove(...)` method.
460+
Also you can completely reset the container by calling `Container.reset()` method.
461+
This will effectively remove all registered services from the container.
330462

331463
## Samples
332464

gulpfile.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@ export class Gulpfile {
5656
@MergedTask()
5757
packageCompile() {
5858
const tsProject = ts.createProject("tsconfig.json");
59-
const tsResult = gulp.src(["src/**/*.ts", "typings/**/*.ts"])
59+
const tsResult = gulp.src(["src/**/*.ts"])
6060
.pipe(sourcemaps.init())
61-
.pipe(ts(tsProject));
61+
.pipe(tsProject());
6262

6363
return [
6464
tsResult.dts.pipe(gulp.dest("build/package")),

sample/sample10-factory-class/Car.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import {Service} from "../../src/decorators/Service";
2+
import {CarFactory} from "./CarFactory";
3+
4+
@Service({ factory: [CarFactory, "create"] })
5+
export class Car {
6+
7+
constructor(public name: string,
8+
public engineName: string,
9+
public wheelCount: number) {
10+
}
11+
12+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import {Service} from "../../src/decorators/Service";
2+
import {Engine} from "./Engine";
3+
import {Wheel} from "./Wheel";
4+
import {Car} from "./Car";
5+
6+
@Service()
7+
export class CarFactory {
8+
9+
constructor(private engine: Engine, private wheel: Wheel) {
10+
}
11+
12+
create() {
13+
return new Car("BMW", this.engine.model, this.wheel.count);
14+
}
15+
16+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import {Service} from "../../src/decorators/Service";
2+
3+
@Service()
4+
export class Engine {
5+
6+
model = "v8";
7+
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import {Service} from "../../src/decorators/Service";
2+
3+
@Service()
4+
export class Wheel {
5+
6+
count = 4;
7+
8+
}

sample/sample10-factory-class/app.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import "reflect-metadata";
2+
import {Container} from "../../src/Container";
3+
import {Car} from "./Car";
4+
5+
const car = Container.get(Car);
6+
console.log(car);
7+

0 commit comments

Comments
 (0)