Skip to content

Commit

Permalink
feat(di/provider): create provide function for instances registration…
Browse files Browse the repository at this point in the history
… to ng container

- provider returns string of proper name to register
- provider register $inject property on Class if there are any service @Inject-ions
  • Loading branch information
Hotell committed Dec 27, 2015
1 parent 637e54c commit b4699fa
Show file tree
Hide file tree
Showing 5 changed files with 372 additions and 2 deletions.
1 change: 1 addition & 0 deletions ng-metadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import './test/life_cycle.spec';
import './test/providers.spec';

import './test/di/decorators.spec';
import './test/di/povider.spec';
import './test/util/decorators.spec';
import './test/reflection/reflection.spec';

Expand Down
80 changes: 80 additions & 0 deletions src/di/provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import {Type,stringify} from '../facade/lang';
import {isFunction} from "../facade/lang";
import {reflector} from "../reflection/reflection";
import {isString} from "../facade/lang";
import {isArray} from "../facade/lang";
import {isType} from "../facade/lang";
import {getTypeName} from "../facade/lang";
import {PipeMetadata} from "../directives/metadata_directives";
import {DirectiveMetadata} from "../directives/metadata_directives";
import {InjectableMetadata} from "./metadata";
import {resolveDirectiveNameFromSelector} from "../facade/lang";
import {InjectMetadata} from "./metadata";

/**
* should extract the string token from provided Type and add $inject angular 1 annotation to constructor if @Inject
* was used
* @param type
* @returns {string}
*/
export function provide( type: Type | string, {useClass}:{useClass?:Type} = {} ): string {

// create $inject annotation if needed
const parameters = isString( type )
? reflector.parameters( useClass )
: reflector.parameters( type );
const injectTo = isString( type )
? useClass
: type;

const $inject = _getInjectStringTokens( parameters );

if ( isArray( $inject ) ) {

(injectTo).$inject = $inject;

}

return provideResolver( type );

}

export function _getInjectStringTokens( parameters: any[][] = [] ): string[] {

return parameters
.filter( ( paramMeta )=>paramMeta.length === 1 && paramMeta[ 0 ] instanceof InjectMetadata )
.map( ( [injectMeta] )=>provideResolver( injectMeta.token ) );

}

export function provideResolver( type: Type | string ): string {

if ( isString( type ) ) {
return type;
}
if ( isType( type ) ) {

// only the first class annotations is injectable
const [annotation=null] = reflector.annotations( type as Type ) || [];

if ( !annotation ) {

return getTypeName( type );

}

if ( annotation instanceof PipeMetadata ) {
return annotation.name;
}

if ( annotation instanceof DirectiveMetadata ) {
return resolveDirectiveNameFromSelector( annotation.selector );
}

if ( annotation instanceof InjectableMetadata ) {
return getTypeName( type );
}

}

}
62 changes: 61 additions & 1 deletion src/facade/lang.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export function isBlank( obj: any ): boolean {
return obj === undefined || obj === null;
}

export function isString( obj: any ): boolean {
export function isString( obj: any ): obj is String {
return typeof obj === "string";
}

Expand Down Expand Up @@ -106,3 +106,63 @@ export function assign( destination: any, ...sources: any[] ): any {
return envAssign( destination, ...sources );

}

const ATTRS_BOUNDARIES = /\[|\]/g;
const COMPONENT_SELECTOR = /^\[?[\w|-]*\]?$/;
const SKEWER_CASE = /-(\w)/g;

export function resolveDirectiveNameFromSelector( selector: string ): string {

if ( !selector.match( COMPONENT_SELECTOR ) ) {
throw new Error(
`Only selectors matching element names or base attributes are supported, got: ${selector}`
);
}

return selector
.trim()
.replace(
ATTRS_BOUNDARIES,
''
)
.replace(
SKEWER_CASE,
( all, letter ) => letter.toUpperCase()
)
}

export function getTypeName(type): string{

const typeName = _getFuncName(type);
return firstToLowerCase( typeName );

}

/**
*
* @param {Function} func
* @returns {string}
* @private
*/
function _getFuncName( func: Function ): string {

const parsedFnStatement = /function\s*([^\s(]+)/.exec(stringify(func));
const [,name=''] = parsedFnStatement || [];

return name || stringify(func);

}

export function controllerKey( name: string ): string {
return '$' + name + 'Controller';
}
export function hasCtorInjectables( Type ): boolean {
return (Array.isArray( Type.$inject ) && Type.$inject.length !== 0);
}
export function firstToLowerCase( value: string ): string {
return value.charAt( 0 ).toLocaleLowerCase() + value.substring( 1 );
}
export function firstToUpperCase( value: string ): string {
return value.charAt( 0 ).toUpperCase() + value.substring( 1 );
}

2 changes: 1 addition & 1 deletion src/reflection/reflection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ import {Reflector} from './reflector';
* The {@link Reflector} used internally in Angular to access metadata
* about symbols.
*/
export var reflector = new Reflector();
export const reflector = new Reflector();
229 changes: 229 additions & 0 deletions test/di/povider.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
import {expect} from 'chai';
import {provide,provideResolver, _getInjectStringTokens} from "../../src/di/provider";
import {Inject,Injectable} from "../../src/di/decorators";
import {InjectMetadata,OptionalMetadata,HostMetadata} from "../../src/di/metadata";
import {Component,Directive,Pipe} from "../../src/directives/decorators";

describe( `di/provider`, ()=> {

describe( `public #provide`, ()=> {

it( `should return string name for Angular registry and add $inject prop if needed (string)`, ()=> {

class Foo{
constructor(@Inject('$http') private $http){

}
}
const actual = provide('fooToken',{useClass:Foo});
const expected = 'fooToken';

expect(actual).to.equal(expected);
expect(Foo.$inject).to.deep.equal(['$http']);

} );
it( `should return string name for Angular registry and add $inject prop if needed (Class)`, ()=> {

class MyService{}

class Foo{
constructor(@Inject(MyService) private mySvc){}
}
const actual = provide(Foo);
const expected = 'foo';

expect(actual).to.equal(expected);
expect(Foo.$inject).to.deep.equal(['myService']);

} );
it( `should return string name for Angular registry and add $inject prop if needed (Pipe)`, ()=> {

class MyService{}

@Pipe({
name: 'fooYo'
})
class FooPipe{
constructor(@Inject(MyService) private mySvc){}
}
const actual = provide(FooPipe);
const expected = 'fooYo';

expect(actual).to.equal(expected);
expect(FooPipe.$inject).to.deep.equal(['myService']);

} );
it( `should return string name for Angular registry and add $inject prop if needed (Directive)`, ()=> {

class MyService{}

@Directive({
selector: '[my-foo]'
})
class FooDirective{
constructor(@Inject(MyService) private mySvc){}
}
const actual = provide(FooDirective);
const expected = 'myFoo';

expect(actual).to.equal(expected);
expect(FooDirective.$inject).to.deep.equal(['myService']);

} );
it( `should return string name for Angular registry and add $inject prop if needed (Component)`, ()=> {

class MyService{}

@Component({
selector: 'my-foo',
template:`hello`
})
class FooComponent{
constructor(@Inject(MyService) private mySvc,@Inject('$element') private $element){}
}
const actual = provide(FooComponent);
const expected = 'myFoo';

expect(actual).to.equal(expected);
expect(FooComponent.$inject).to.deep.equal(['myService','$element']);

} );

// @TODO
it.skip( `should work as factory with angular.module.*.apply and output array [injectName,typeFunction]`, ()=> {

/*class MyService{}
class Foo{
constructor(@Inject(MyService) private mySvc){}
}
let actual = provide(Foo);
let expected = [ 'foo', Foo ];
expect(actual).to.deep.equal(expected);
expect(Foo.$inject).to.deep.equal(['myService']);
@Directive( {
selector: '[my-attr]'
} )
class FooDirective{}
let directiveProvider = provide(FooDirective) as [string,Function];
actual = [ directiveProvider[ 0 ], directiveProvider[ 1 ]() ];
expected = [ 'myAttr', {
controller: FooDirective,
link: function _postLink(){}
} ];
expect(actual).to.deep.equal(expected);
expect(FooDirective.$inject).to.deep.equal(['myService']);*/

} );

} );

describe( `#_getInjectStringTokens`, ()=> {

it( `should create proper $inject string array`, ()=> {

class MyService{}

@Injectable()
class AnotherService {
}

const parameters = [
[new InjectMetadata('foo')],
[new InjectMetadata(MyService)],
[new InjectMetadata('boo')],
[new InjectMetadata(AnotherService)],
[new InjectMetadata('nope'),new OptionalMetadata(), new HostMetadata()]
];

const actual = _getInjectStringTokens(parameters);
const expected = ['foo','myService','boo','anotherService'];

expect( actual ).to.deep.equal( expected );

} );

} );

describe( `#provideResolver`, ()=> {

it( `should get DI container string name if string`, ()=> {

const actual = provideResolver( '$http' );
const expected = '$http';

expect( actual ).to.equal( expected );

} );

it( `should get DI container string name if service Class`, ()=> {

class MyService{}

const actual = provideResolver( MyService );
const expected = 'myService';

expect( actual ).to.equal( expected );

} );
it( `should get DI container string name if Injectable Class`, ()=> {

@Injectable()
class MyService{}

const actual = provideResolver( MyService );
const expected = 'myService';

expect( actual ).to.equal( expected );

} );
it( `should get DI container string name if Directive Class`, ()=> {

@Directive({
selector:'[my-attr]'
})
class MyDirective{}

const actual = provideResolver( MyDirective );
const expected = 'myAttr';

expect( actual ).to.equal( expected );


} );
it( `should get DI container string name if Component Class`, ()=> {

@Component({
selector:'my-cmp'
})
class MyComponent{}

const actual = provideResolver( MyComponent );
const expected = 'myCmp';

expect( actual ).to.equal( expected );

} );
it( `should get DI container string name if Pipe Class`, ()=> {

@Pipe({
name:'myPipeYo'
})
class MyPipe{}

const actual = provideResolver( MyPipe );
const expected = 'myPipeYo';

expect( actual ).to.equal( expected );

} );


} );

} );

0 comments on commit b4699fa

Please sign in to comment.