Skip to content

Commit f09ff03

Browse files
Merge pull request #162 from doktordirk/resolver-meta
Fixing parameter decorators
2 parents 6b8ab44 + f7b70f0 commit f09ff03

File tree

4 files changed

+445
-131
lines changed

4 files changed

+445
-131
lines changed

doc/article/en-US/dependency-injection-basics.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -325,14 +325,14 @@ As mentioned above, the DI container uses `Resolvers` internally to provide all
325325
* `NewInstance` - Used to inject a new instance of a dependency, without regard for existing instances in the container.
326326
* ex. `NewInstance.of(CustomClass).as(Another)`
327327

328-
If using TypeScript, keep in mind that `@autoinject` won't allow you to use `Resolvers`. Instead, you may use argument decorators, without duplicating argument order, which you otherwise have to maintain when using the class decorator or the static `inject` property. Available function parameter decorators are:
328+
If using TypeScript, keep in mind that `@autoinject` won't allow you to use `Resolvers`. Instead, you may use argument decorators, without duplicating argument order, which you otherwise have to maintain when using the class decorator or the static `inject` property. You also can use `inject` as argument decorator for your own custom resolvers, eg `constructor(@inject(NewInstance.of(HttpClient)) public client: HttpClient){...}`. Available build-in function parameter decorators are:
329329

330330
* `lazy(key)`
331331
* `all(key)`
332332
* `optional(checkParent?)`
333333
* `parent`
334334
* `factory(key, asValue?)`
335-
* `newInstance(key?)`
335+
* `newInstance(asKey?, dynamicDependencies: [any])`
336336

337337
Here's an example of how we might express a dependency on `HttpClient` that we may or may not actually need to use, depending on runtime scenarios:
338338

src/injection.js

+8-29
Original file line numberDiff line numberDiff line change
@@ -6,46 +6,25 @@ import {_emptyParameters} from './container';
66
*/
77
export function autoinject(potentialTarget?: any): any {
88
let deco = function(target) {
9-
let previousInject = target.inject ? target.inject.slice() : null; //make a copy of target.inject to avoid changing parent inject
10-
let autoInject: any = metadata.getOwn(metadata.paramTypes, target) || _emptyParameters;
11-
if (!previousInject) {
12-
target.inject = autoInject;
13-
} else {
14-
for (let i = 0; i < autoInject.length; i++) {
15-
//check if previously injected.
16-
if (previousInject[i] && previousInject[i] !== autoInject[i]) {
17-
const prevIndex = previousInject.indexOf(autoInject[i]);
18-
if (prevIndex > -1) {
19-
previousInject.splice(prevIndex, 1);
20-
}
21-
previousInject.splice((prevIndex > -1 && prevIndex < i) ? i - 1 : i, 0, autoInject[i]);
22-
} else if (!previousInject[i]) {//else add
23-
previousInject[i] = autoInject[i];
24-
}
25-
}
26-
target.inject = previousInject;
9+
if (!target.hasOwnProperty('inject')) {
10+
target.inject = (metadata.getOwn(metadata.paramTypes, target) || _emptyParameters).slice();
2711
}
2812
};
2913

3014
return potentialTarget ? deco(potentialTarget) : deco;
3115
}
3216

3317
/**
34-
* Decorator: Specifies the dependencies that should be injected by the DI Container into the decoratored class/function.
18+
* Decorator: Specifies the dependencies that should be injected by the DI Container into the decorated class/function.
3519
*/
3620
export function inject(...rest: any[]): any {
3721
return function(target, key, descriptor) {
38-
// handle when used as a parameter
39-
if (typeof descriptor === 'number' && rest.length === 1) {
40-
let params = target.inject;
41-
if (typeof params === 'function') {
42-
throw new Error('Decorator inject cannot be used with "inject()". Please use an array instead.');
43-
}
44-
if (!params) {
45-
params = metadata.getOwn(metadata.paramTypes, target).slice();
46-
target.inject = params;
22+
// handle when used as a constructor parameter decorator
23+
if (typeof descriptor === 'number') {
24+
autoinject(target);
25+
if (rest.length === 1) {
26+
target.inject[descriptor] = rest[0];
4727
}
48-
params[descriptor] = rest[0];
4928
return;
5029
}
5130
// if it's true then we injecting rest into function and not Class constructor

src/resolvers.js

+75-70
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {protocol} from 'aurelia-metadata';
22
import {Container} from './container';
3+
import {autoinject} from './injection';
34

45
/**
56
* Decorator: Indicates that the decorated class/object is a custom resolver.
@@ -25,6 +26,53 @@ export interface Resolver {
2526
get(container: Container, key: any): any;
2627
}
2728

29+
/**
30+
* Used to resolve instances, singletons, transients, aliases
31+
*/
32+
@resolver()
33+
export class StrategyResolver {
34+
strategy: StrategyResolver | number;
35+
state: any;
36+
37+
/**
38+
* Creates an instance of the StrategyResolver class.
39+
* @param strategy The type of resolution strategy.
40+
* @param state The state associated with the resolution strategy.
41+
*/
42+
constructor(strategy, state) {
43+
this.strategy = strategy;
44+
this.state = state;
45+
}
46+
47+
/**
48+
* Called by the container to allow custom resolution of dependencies for a function/class.
49+
* @param container The container to resolve from.
50+
* @param key The key that the resolver was registered as.
51+
* @return Returns the resolved object.
52+
*/
53+
get(container: Container, key: any): any {
54+
switch (this.strategy) {
55+
case 0: //instance
56+
return this.state;
57+
case 1: //singleton
58+
let singleton = container.invoke(this.state);
59+
this.state = singleton;
60+
this.strategy = 0;
61+
return singleton;
62+
case 2: //transient
63+
return container.invoke(this.state);
64+
case 3: //function
65+
return this.state(container, key, this);
66+
case 4: //array
67+
return this.state[0].get(container, key);
68+
case 5: //alias
69+
return container.get(this.state);
70+
default:
71+
throw new Error('Invalid strategy: ' + this.strategy);
72+
}
73+
}
74+
}
75+
2876
/**
2977
* Used to allow functions/classes to specify lazy resolution logic.
3078
*/
@@ -140,7 +188,6 @@ export class Optional {
140188
}
141189
}
142190

143-
144191
/**
145192
* Used to inject the dependency from the parent container instead of the current one.
146193
*/
@@ -178,50 +225,6 @@ export class Parent {
178225
}
179226
}
180227

181-
@resolver()
182-
export class StrategyResolver {
183-
strategy: StrategyResolver | number;
184-
state: any;
185-
186-
/**
187-
* Creates an instance of the StrategyResolver class.
188-
* @param strategy The type of resolution strategy.
189-
* @param state The state associated with the resolution strategy.
190-
*/
191-
constructor(strategy, state) {
192-
this.strategy = strategy;
193-
this.state = state;
194-
}
195-
196-
/**
197-
* Called by the container to allow custom resolution of dependencies for a function/class.
198-
* @param container The container to resolve from.
199-
* @param key The key that the resolver was registered as.
200-
* @return Returns the resolved object.
201-
*/
202-
get(container: Container, key: any): any {
203-
switch (this.strategy) {
204-
case 0: //instance
205-
return this.state;
206-
case 1: //singleton
207-
let singleton = container.invoke(this.state);
208-
this.state = singleton;
209-
this.strategy = 0;
210-
return singleton;
211-
case 2: //transient
212-
return container.invoke(this.state);
213-
case 3: //function
214-
return this.state(container, key, this);
215-
case 4: //array
216-
return this.state[0].get(container, key);
217-
case 5: //alias
218-
return container.get(this.state);
219-
default:
220-
throw new Error('Invalid strategy: ' + this.strategy);
221-
}
222-
}
223-
}
224-
225228
/**
226229
* Used to allow injecting dependencies but also passing data to the constructor.
227230
*/
@@ -270,8 +273,12 @@ export class Factory {
270273
*/
271274
@resolver()
272275
export class NewInstance {
273-
key;
274-
asKey;
276+
/** @internal */
277+
key: any;
278+
/** @internal */
279+
asKey: any;
280+
/** @internal */
281+
dynamicDependencies: any[];
275282

276283
/**
277284
* Creates an instance of the NewInstance class.
@@ -327,26 +334,24 @@ export class NewInstance {
327334
}
328335
}
329336

330-
export function getDecoratorDependencies(target, name) {
331-
let dependencies = target.inject;
332-
if (typeof dependencies === 'function') {
333-
throw new Error('Decorator ' + name + ' cannot be used with "inject()". Please use an array instead.');
334-
}
335-
if (!dependencies) {
336-
dependencies = metadata.getOwn(metadata.paramTypes, target).slice();
337-
target.inject = dependencies;
338-
}
337+
/**
338+
* Used by parameter decorators to call autoinject for the target and retrieve the target's inject property.
339+
* @param target The target class.
340+
* @return Returns the target's own inject property.
341+
*/
342+
export function getDecoratorDependencies(target) {
343+
autoinject(target);
339344

340-
return dependencies;
345+
return target.inject;
341346
}
342347

343348
/**
344349
* Decorator: Specifies the dependency should be lazy loaded
345350
*/
346351
export function lazy(keyValue: any) {
347352
return function(target, key, index) {
348-
let params = getDecoratorDependencies(target, 'lazy');
349-
params[index] = Lazy.of(keyValue);
353+
let inject = getDecoratorDependencies(target);
354+
inject[index] = Lazy.of(keyValue);
350355
};
351356
}
352357

@@ -355,8 +360,8 @@ export function lazy(keyValue: any) {
355360
*/
356361
export function all(keyValue: any) {
357362
return function(target, key, index) {
358-
let params = getDecoratorDependencies(target, 'all');
359-
params[index] = All.of(keyValue);
363+
let inject = getDecoratorDependencies(target);
364+
inject[index] = All.of(keyValue);
360365
};
361366
}
362367

@@ -366,8 +371,8 @@ export function all(keyValue: any) {
366371
export function optional(checkParentOrTarget: boolean = true) {
367372
let deco = function(checkParent: boolean) {
368373
return function(target, key, index) {
369-
let params = getDecoratorDependencies(target, 'optional');
370-
params[index] = Optional.of(params[index], checkParent);
374+
let inject = getDecoratorDependencies(target);
375+
inject[index] = Optional.of(inject[index], checkParent);
371376
};
372377
};
373378
if (typeof checkParentOrTarget === 'boolean') {
@@ -380,18 +385,18 @@ export function optional(checkParentOrTarget: boolean = true) {
380385
* Decorator: Specifies the dependency to look at the parent container for resolution
381386
*/
382387
export function parent(target, key, index) {
383-
let params = getDecoratorDependencies(target, 'parent');
384-
params[index] = Parent.of(params[index]);
388+
let inject = getDecoratorDependencies(target);
389+
inject[index] = Parent.of(inject[index]);
385390
}
386391

387392
/**
388393
* Decorator: Specifies the dependency to create a factory method, that can accept optional arguments
389394
*/
390395
export function factory(keyValue: any, asValue?: any) {
391396
return function(target, key, index) {
392-
let params = getDecoratorDependencies(target, 'factory');
397+
let inject = getDecoratorDependencies(target);
393398
let factory = Factory.of(keyValue);
394-
params[index] = asValue ? factory.as(asValue) : factory;
399+
inject[index] = asValue ? factory.as(asValue) : factory;
395400
};
396401
}
397402

@@ -401,10 +406,10 @@ export function factory(keyValue: any, asValue?: any) {
401406
export function newInstance(asKeyOrTarget?: any, ...dynamicDependencies: any[]) {
402407
let deco = function(asKey?: any) {
403408
return function(target, key, index) {
404-
let params = getDecoratorDependencies(target, 'newInstance');
405-
params[index] = NewInstance.of(params[index], ...dynamicDependencies);
409+
let inject = getDecoratorDependencies(target);
410+
inject[index] = NewInstance.of(inject[index], ...dynamicDependencies);
406411
if (!!asKey) {
407-
params[index].as(asKey);
412+
inject[index].as(asKey);
408413
}
409414
};
410415
};

0 commit comments

Comments
 (0)