Skip to content

Commit 000553d

Browse files
committed
feat(core): support Injection by Type via reflect-metadata design:paramtypes
- from now on you can DI via typescript type information if you want to inject a class instance instead of using @Inject() for everything Closes: #41 BREAKING CHANGES: - from now on ngMetadata leverages reflect-metadata polyfill, so you need to include it to your app - also you need to add `"emitDecoratorMetadata": true` to your tsconfig.json
1 parent 5a8ef71 commit 000553d

17 files changed

+572
-142
lines changed

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"ghooks": "1.0.1",
3838
"live-server": "0.8.2",
3939
"mocha": "2.3.4",
40+
"reflect-metadata": "0.1.3",
4041
"sinon": "1.17.2",
4142
"systemjs": "0.19.6",
4243
"systemjs-builder": "0.14.11",
@@ -45,7 +46,8 @@
4546
"validate-commit-msg": "2.0.0"
4647
},
4748
"peerDependencies": {
48-
"angular": ">=1.4.x"
49+
"angular": ">=1.4.x",
50+
"reflect-metadata": "0.1.x"
4951
},
5052
"dependencies": {},
5153
"czConfig": {

src/core/di/provider.ts

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { pipeProvider } from '../pipes/pipe_provider';
2424
import { directiveProvider } from '../directives/directive_provider';
2525
import { ListWrapper } from '../../facade/collections';
2626
import { resolveForwardRef } from './forward_ref';
27+
import { getErrorMsg } from '../../facade/exceptions';
2728

2829

2930
export type PropMetaInst = InputMetadata | OutputMetadata | HostBindingMetadata | HostListenerMetadata;
@@ -161,13 +162,14 @@ export function _dependenciesFor(typeOrFunc: Type): string[] {
161162

162163
if ( isBlank( params ) ) return [];
163164

164-
if ( params.some( isBlank ) ) {
165+
if ( params.some( ( param ) => isBlank( param ) || ListWrapper.isEmpty( param ) ) ) {
165166

166-
throw new Error( `
167-
${ stringify( typeOrFunc ) } :
168-
-------------------------------------------------
169-
you cannot have holes in constructor DI injection
170-
` );
167+
throw new Error(
168+
getErrorMsg(
169+
typeOrFunc,
170+
`you cannot have holes in constructor DI injection`
171+
)
172+
);
171173

172174
}
173175

@@ -184,16 +186,19 @@ export function _dependenciesFor(typeOrFunc: Type): string[] {
184186
*/
185187
export function _extractToken( metadata: ParamMetaInst[] ): string {
186188

187-
const [injectMetadata] = metadata
188-
.filter( paramMetadata=>paramMetadata instanceof InjectMetadata ) as InjectMetadata[];
189+
// this is token obtained via design:paramtypes via Reflect.metadata
190+
const [paramMetadata] = metadata.filter( isType );
191+
// this is token obtained from @Inject() usage for DI
192+
const [injectMetadata] = metadata.filter( isInjectMetadata ) as InjectMetadata[];
189193

190-
if(isBlank(injectMetadata)){
194+
if(isBlank(injectMetadata) && isBlank(paramMetadata)){
191195
return;
192196
}
193197

194-
const {token} = injectMetadata;
198+
const { token=undefined } = injectMetadata || {};
199+
const injectable = resolveForwardRef( token ) || paramMetadata;
195200

196-
return getInjectableName( resolveForwardRef( token ) );
201+
return getInjectableName( injectable );
197202

198203
}
199204

@@ -239,6 +244,7 @@ export function getInjectableName(injectable: ProviderType): string{
239244
// class SomeService(){}
240245
//
241246
// @Inject(SomeService) someSvc
247+
// someSvc: SomeService
242248
if ( isType( injectable ) ) {
243249

244250
// only the first class annotations is injectable
@@ -318,3 +324,6 @@ function isService(annotation: InjectableMetadata): boolean{
318324
function isPipe(annotation: PipeMetadata): boolean{
319325
return isString(annotation.name) && annotation instanceof PipeMetadata;
320326
}
327+
function isInjectMetadata( injectMeta: any ): injectMeta is InjectMetadata {
328+
return injectMeta instanceof InjectMetadata;
329+
}

src/core/linker/directive_resolver.ts

Lines changed: 69 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
import { InjectMetadata, HostMetadata, SelfMetadata, SkipSelfMetadata, OptionalMetadata } from '../di/metadata';
2020
import { ParamMetaInst, PropMetaInst, getInjectableName } from '../di/provider';
2121
import { resolveForwardRef } from '../di/forward_ref';
22+
import { getErrorMsg } from '../../facade/exceptions';
2223

2324
function _isDirectiveMetadata( type: any ): boolean {
2425
return type instanceof DirectiveMetadata;
@@ -27,49 +28,82 @@ function _isDirectiveMetadata( type: any ): boolean {
2728
/**
2829
* return required string map for provided local DI
2930
* ```typescript
30-
* // for constructor(@Inject('ngModel') @Self() @Optional() ngModel){}
31+
* // for
32+
* constructor(@Inject('ngModel') @Self() @Optional() ngModel){}
3133
* // it returns:
3234
* { ngModel: '?ngModel' }
35+
*
36+
* // when MyComponent is
37+
* @Component({ selector: 'myCoolCmp', template:`hello`})
38+
* class MyComponent{}
39+
* // for
40+
* constructor(@Host() @Optional() myCmp: MyComponent){}
41+
* // it returns:
42+
* { myCmp: '^myCoolCmp' }
3343
* ```
3444
* @param paramsMeta
45+
* @param idx
46+
* @param typeOrFunc
3547
* @returns {{[directiveName]:string}}
3648
* @private
3749
*/
38-
function _transformInjectedDirectivesMeta( paramsMeta: ParamMetaInst[], idx: number ): StringMap {
50+
function _transformInjectedDirectivesMeta( paramsMeta: ParamMetaInst[], idx: number, typeOrFunc: Type ): StringMap {
3951

40-
// if there is just @Inject return
41-
if ( paramsMeta.length < 2 ) {
42-
return;
43-
}
52+
if ( !_isInjectableParamsDirective( paramsMeta ) ) { return }
4453

54+
// @TODO unite this with _extractToken from provider.ts
4555
const injectInst = ListWrapper.find( paramsMeta, param=>param instanceof InjectMetadata ) as InjectMetadata;
46-
47-
if ( !injectInst.token ) {
48-
throw new Error( `no Directive instance name provided within @Inject()` );
56+
const injectType = ListWrapper.find( paramsMeta, isType ) as Type;
57+
const { token=undefined } = injectInst || { token: injectType };
58+
// we need to decrement param count if user uses both @Inject() and :MyType
59+
const paramsMetaLength = (injectInst && injectType)
60+
? paramsMeta.length - 1
61+
: paramsMeta.length;
62+
63+
if ( !token ) {
64+
throw new Error(
65+
getErrorMsg(
66+
typeOrFunc,
67+
`no Directive instance name provided within @Inject() or :DirectiveClass annotation missing`
68+
)
69+
);
4970
}
5071

5172
const isHost = ListWrapper.findIndex( paramsMeta, param=>param instanceof HostMetadata ) !== -1;
5273
const isOptional = ListWrapper.findIndex( paramsMeta, param=>param instanceof OptionalMetadata ) !== -1;
5374
const isSelf = ListWrapper.findIndex( paramsMeta, param=>param instanceof SelfMetadata ) !== -1;
5475
const isSkipSelf = ListWrapper.findIndex( paramsMeta, param=>param instanceof SkipSelfMetadata ) !== -1;
5576

56-
if ( isOptional && paramsMeta.length !== 3 ) {
57-
throw new Error( `
58-
you cannot use @Optional() without related decorator for injecting Directives. use one of @Host|@Self()|@SkipSelf() + @Optional()`
77+
if ( isOptional && paramsMetaLength !== 3 ) {
78+
throw new Error(
79+
getErrorMsg(
80+
typeOrFunc,
81+
`you cannot use @Optional() without related decorator for injecting Directives. use one of @Host|@Self()|@SkipSelf() + @Optional()`
82+
)
5983
);
6084
}
6185
if ( isSelf && isSkipSelf ) {
62-
throw new Error( `you cannot provide both @Self() and @SkipSelf() with @Inject(${injectInst.token}) for Directive Injection` );
86+
throw new Error(
87+
getErrorMsg(
88+
typeOrFunc,
89+
`you cannot provide both @Self() and @SkipSelf() with @Inject(${getFuncName( token )}) for Directive Injection`
90+
)
91+
);
6392
}
6493
if( (isHost && isSelf) || (isHost && isSkipSelf)){
65-
throw new Error( `you cannot provide both @Host(),@SkipSelf() or @Host(),@Self() with @Inject(${getFuncName(injectInst.token)}) for Directive Injections` );
94+
throw new Error(
95+
getErrorMsg(
96+
typeOrFunc,
97+
`you cannot provide both @Host(),@SkipSelf() or @Host(),@Self() with @Inject(${getFuncName( token )}) for Directive Injections`
98+
)
99+
);
66100
}
67101

68102
const locateType = _getLocateTypeSymbol();
69103
const optionalType = isOptional ? '?' : '';
70104

71105
const requireExpressionPrefix = `${ optionalType }${ locateType }`;
72-
const directiveName = _getDirectiveName( injectInst.token );
106+
const directiveName = _getDirectiveName( token );
73107

74108
// we need to generate unique names because if we require same directive controllers,
75109
// with different locale decorators it would merge to one which is wrong
@@ -99,6 +133,25 @@ function _transformInjectedDirectivesMeta( paramsMeta: ParamMetaInst[], idx: num
99133

100134
}
101135

136+
// exit if user uses both @Inject() and :Type for DI because this is not directive injection
137+
function _isInjectableParamsDirective( paramsMeta: ParamMetaInst[] ): boolean {
138+
139+
// if there is just @Inject or Type from design:paramtypes return
140+
if ( paramsMeta.length < 2 ) {
141+
return false;
142+
}
143+
144+
if ( paramsMeta.length === 2 ) {
145+
const injectableParamCount = paramsMeta.filter( inj => inj instanceof InjectMetadata || isType( inj ) ).length;
146+
if ( injectableParamCount === 2 ) {
147+
return false;
148+
}
149+
}
150+
151+
return true;
152+
153+
}
154+
102155
}
103156

104157
/**
@@ -148,7 +201,7 @@ export class DirectiveResolver {
148201
return paramMetadata
149202
.reduce( ( acc, paramMetaArr, idx )=> {
150203

151-
const requireExp = _transformInjectedDirectivesMeta( paramMetaArr, idx );
204+
const requireExp = _transformInjectedDirectivesMeta( paramMetaArr, idx, type );
152205
if ( isPresent( requireExp ) ) {
153206
assign( acc, requireExp );
154207
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Type } from '../../facade/lang';
2+
import { GetterFn, SetterFn, MethodFn } from './types';
3+
4+
export interface PlatformReflectionCapabilities {
5+
isReflectionEnabled(): boolean;
6+
factory( type: Type ): Function;
7+
interfaces( type: Type ): any[];
8+
9+
parameters( type: any ): any[][],
10+
rawParameters(typeOrFunc: Type): any[][],
11+
registerParameters( parameters, type: Type ): void,
12+
13+
annotations( type: any ): any[],
14+
ownAnnotations(typeOrFunc: Type): any[],
15+
registerAnnotations( annotations, typeOrFunc: Type ): void,
16+
17+
propMetadata( typeOrFunc: any ): {[key: string]: any[]},
18+
ownPropMetadata( typeOrFunc: Type ): {[key: string]: any[]},
19+
registerPropMetadata( propMetadata, typeOrFunc: Type ): void,
20+
21+
getter( name: string ): GetterFn;
22+
setter( name: string ): SetterFn;
23+
method( name: string ): MethodFn;
24+
}

src/core/reflection/reflection.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import {Reflector} from './reflector';
1+
import { Reflector } from './reflector';
2+
import { ReflectionCapabilities } from './reflection_capabilities';
23

34
/**
45
* The {@link Reflector} used internally in Angular to access metadata
56
* about symbols.
67
*/
7-
export const reflector = new Reflector();
8+
export const reflector = new Reflector( new ReflectionCapabilities() );

0 commit comments

Comments
 (0)