Skip to content

Commit

Permalink
Merge pull request #180 from ngParty/upgrade/static
Browse files Browse the repository at this point in the history
  • Loading branch information
Hotell authored Dec 24, 2016
2 parents 821b772 + 2ea1299 commit d0c0e5d
Show file tree
Hide file tree
Showing 12 changed files with 723 additions and 16 deletions.
10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
},
"homepage": "https://github.com/ngParty/ng-metadata#readme",
"devDependencies": {
"@angular/core": "2.4.1",
"@types/angular": "1.5.19",
"@types/chai": "3.4.34",
"@types/jquery": "2.0.34",
Expand All @@ -47,15 +48,16 @@
"live-server": "0.8.2",
"mocha": "3.1.2",
"reflect-metadata": "0.1.8",
"rxjs": "5.0.0-rc.1",
"rxjs": "5.0.1",
"sinon": "1.17.6",
"systemjs": "0.19.6",
"ts-node": "1.7.0",
"typescript": "2.2.0-dev.20161115",
"validate-commit-msg": "2.0.0"
"typescript": "2.1.4",
"validate-commit-msg": "2.0.0",
"zone.js": "^0.7.2"
},
"peerDependencies": {
"rxjs": "5.0.0-rc.1",
"rxjs": "^5.0.1",
"angular": ">=1.4.x"
},
"dependencies": {},
Expand Down
136 changes: 136 additions & 0 deletions src/upgrade/static/downgrade_component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { Type } from '../../facade/type';
import { reflector } from '../../core/reflection/reflection';
import { resolveDirectiveNameFromSelector, isString } from '../../facade/lang';
import { StringMapWrapper } from '../../facade/collections';

export type ProvideNg2ComponentParams = {
component:Type,
downgradeFn:downgradeComponent
};
export type downgradeComponent = (info: {
component: Type,
inputs?: string[],
outputs?: string[],
}) => any;


/**
* Used to register an Angular 2 Component as a directive on an Angular 1 module,
* where the directive name and bindings(inputs,outputs) are automatically created from the selector.
*
* @example
* ```typescript
* // app.module.ts
* import * as angular from 'angular'
* import { downgradeComponent } from '@angular/upgrade/static/';
* import { downgradeNg2Component } from 'ng-metadata/upgrade';
* import { provide } from 'ng-metadata/core';
*
* export const AppModule = angular
* .module('myApp',[])
* .directive(...downgradeNg2Component({component:Ng2Component,downgradeFn:downgradeComponent}))
* ```
*/
export function downgradeNg2Component({component,downgradeFn}: ProvideNg2ComponentParams): [string,Function] {
const {name,factoryFn} = _downgradeComponent({component,downgradeFn});
return [name,factoryFn]
}

/**
* Used to register an Angular 2 Component by including it in the `declarations` array of an ng-metadata `@NgModule`,
* where the directive name and bindings(inputs,outputs) are automatically created from the selector.
*
* @example
* ```typescript
* // app.module.ts
* import { downgradeComponent } from '@angular/upgrade/static/';
* import { provideNg2Component } from 'ng-metadata/upgrade';
* import { NgModule } from 'ng-metadata/core';
*
* @NgModule({
* declarations:[
* provideNg2Component({component:Ng2Component,downgradeFn:downgradeComponent})
* ]
* })
* export class AppModule {};
* ```
*/
export function provideNg2Component({component,downgradeFn}: ProvideNg2ComponentParams): Function {
const {name,factoryFn} = _downgradeComponent({component,downgradeFn});

reflector.registerDowngradedNg2ComponentName(name, factoryFn);
return factoryFn;
}

/**
*
* @private
* @internal
*/
export function _downgradeComponent({component,downgradeFn}: ProvideNg2ComponentParams): {name:string, factoryFn:Function} {
// process inputs,outputs
const propAnnotations = reflector.propMetadata(component);
const {inputs=[],outputs=[]} = _getOnlyInputOutputMetadata(propAnnotations) || {};

// process @Component
const annotations = reflector.annotations(component);
const cmpAnnotation = annotations[0];
const directiveName = resolveDirectiveNameFromSelector(cmpAnnotation.selector);

const downgradedDirectiveFactory = downgradeFn(
StringMapWrapper.assign(
{},
inputs.length ? {inputs: inputs} : {},
outputs.length ? {outputs: outputs} : {},
{component: component},
)
);
return {
name: directiveName,
factoryFn: downgradedDirectiveFactory
};
}

type Ng2InputOutputPropDecoratorFactory = [{
bindingPropertyName?: string,
toString(): string
}]
function _getOnlyInputOutputMetadata(metadata:{[propName:string]:Ng2InputOutputPropDecoratorFactory[]}){
if(StringMapWrapper.isEmpty( metadata)){
return;
}
const inputOutput = {
inputs: [],
outputs: []
};
StringMapWrapper.forEach(metadata, (metaItem: Ng2InputOutputPropDecoratorFactory, key: string) => {
if(_isNg2InputPropDecoratorFactory(metaItem)){
inputOutput.inputs.push(
_createBindingFromNg2PropDecoratorFactory(key,metaItem[0].bindingPropertyName)
);
return;
}
if(_isNg2OutputPropDecoratorFactory(metaItem)){
inputOutput.outputs.push(
_createBindingFromNg2PropDecoratorFactory(key,metaItem[0].bindingPropertyName)
);
return;
}
});
return inputOutput;
}
function _createBindingFromNg2PropDecoratorFactory(prop:string,attr?:string): string {
return isString(attr) ? `${prop}: ${attr}` : `${prop}`;
}
function _isNg2InputPropDecoratorFactory(metadataValues:any[]): boolean{
return _isNg2InputOutputPropDecoratorFactory(metadataValues,'@Input');
}
function _isNg2OutputPropDecoratorFactory(metadataValues:any[]): boolean{
return _isNg2InputOutputPropDecoratorFactory(metadataValues,'@Output');
}
function _isNg2InputOutputPropDecoratorFactory(metadataValues:any[], type:'@Input'|'@Output'): boolean {
return metadataValues.some((metaValue:any)=>{
const decoratorType = metaValue.toString();
return decoratorType === type;
})
}
157 changes: 157 additions & 0 deletions src/upgrade/static/downgrade_injectable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import { OpaqueToken } from '../../core/di/opaque_token';
import { getInjectableName } from '../../core/di/provider';
import { ProviderLiteral } from '../../core/di/provider_util';
import { Type } from '../../facade/type';

export type ProvideNg2InjectableParams = {
injectable: Function | Type,
downgradeFn: Function,
/**
* We need token only if downgraded ANgular 2 Service is not Decorated with both ng2 @Injectable and ngMetadata @Injectable
*
*/
token?: string | OpaqueToken,
}

/**
* Downgrades an Angular 2 Injectable so that it can be registered as an Angular 1
* factory. Either a string or an ng-metadata OpaqueToken can be used for the name.
*
* **NOTE:** downgraded service must also be registered within Angular 2 Component or NgModule
*
* @example
* ```typescript
* // app.module.ts
* import * as angular from 'angular'
* import { downgradeInjectable } from '@angular/upgrade/static/';
* import { downgradeNg2Injectable } from 'ng-metadata/upgrade';
* import { provide } from 'ng-metadata/core';
*
* import { Ng2Service } from './services/ng2.service;
* import { Ng2ServiceDecorated } from './services/ng2decorated.service
*
* export const OtherServiceToken = new OpaqueToken('otherService')
*
* export const AppModule = angular
* .module('myApp',[])
* .factory(...downgradeNg2Injectable({token:'ng2Service', injectable: Ng2Service, downgradeFn: downgradeInjectable }))
* .factory(...downgradeNg2Injectable({token: OtherServiceToken, injectable: Ng2Service, downgradeFn: downgradeInjectable }))
* .factory(...downgradeNg2Injectable({injectable:Ng2ServiceDecorated, downgradeFn: downgradeInjectable}))
* ```
*/
export function downgradeNg2Injectable( { injectable, downgradeFn, token }: ProvideNg2InjectableParams ): [string|Function] {
const { name, factoryFn } = _downgradeInjectable( {
token: token || injectable as any,
injectable,
downgradeFn
} );
return [ name, factoryFn ]
}


/**
* Returns a ProviderLiteral which can be used to register an Angular 2 Provider/Injectable
* by including it in the providers array of an ng-metadata annotated Angular 1
* Component or NgModule. Either a string or an ng-metadata OpaqueToken can be used for the name.
*
* **NOTE:** downgraded service must also be registered within Angular 2 Component or NgModule
*
* @example
* ```
* // foo.component.ts - Angular 1(ngMetadata)
* import { downgradeInjectable } from '@angular/upgrade/static/';
* import { provideNg2Injectable } from 'ng-metadata/upgrade';
* import { Component } from 'ng-metadata/core';
*
* import { Ng2Service } from './services/ng2.service;
* import { Ng2ServiceDecorated } from './services/ng2decorated.service;
*
* const OtherServiceToken = new OpaqueToken('otherService')
*
* @Component({
* selector: 'my-foo',
* providers: [
* provideNg2Injectable({token:'ng2Service', injectable: Ng2Service, downgradeFn: downgradeInjectable }),
* provideNg2Injectable({token:OtherServiceToken, injectable: Ng2Service, downgradeFn: downgradeInjectable }),
* provideNg2Injectable({injectable:Ng2ServiceDecorated, downgradeFn: downgradeInjectable}),
* ],
* })
* class FooComponent{}
* ```
*
* or via ngMetadata NgModule:
*
* @example
* ```typescript
* * @example
* ```
* // app.module.ts - Angular 1(ngMetadata)
* import { downgradeInjectable } from '@angular/upgrade/static/';
* import { provideNg2Injectable } from 'ng-metadata/upgrade';
* import { NgModule } from 'ng-metadata/core';
*
* import { Ng2Service } from './services/ng2.service;
* import { Ng2ServiceDecorated } from './services/ng2decorated.service
*
* const OtherServiceToken = new OpaqueToken('otherService')
*
* @NgModule({
* providers: [
* provideNg2Injectable({token:'ng2Service', injectable: Ng2Service, downgradeFn: downgradeInjectable }),
* provideNg2Injectable({token:OtherServiceToken, injectable: Ng2Service, downgradeFn: downgradeInjectable }),
* provideNg2Injectable({injectable:Ng2ServiceDecorated, downgradeFn: downgradeInjectable}),
* ],
* })
* export class AppModule{}
* ```
*
* as you've may noticed in one registration we've omitted `token`, how is that possible that it works you ask?
* this is thanks to ngMetadata `@Injectable()` decorator, we can decorate Angular 2 Classes with our ngMetadata `@Injectable`,
* which gives us benefit to omit Opaque tokens creation and use the same class for DI for both Angular 2 and Angular 1.
* POWER OVERWHELMING RIGHT?!
*
* Enough Talk! Show me how the service looks like:
* ```typescript
* // ./services/ng2decorated.service.ts
*
* import {Injectable} from '@angular/core';
* import {Injectable as KeepNg1Injectable} from 'ng-metadata/core';
*
* @KeepNg1Injectable()
* @Injectable()
* export class Ng2ServiceDecorated {
* constructor(){}
* greet(){}
* }
* ```
*/
export function provideNg2Injectable( { injectable, downgradeFn, token }: ProvideNg2InjectableParams ): ProviderLiteral {
const { name, factoryFn, deps } = _downgradeInjectable( {
token: token || injectable as any,
injectable,
downgradeFn
} );

return {
provide: name,
useFactory: factoryFn,
deps: deps,
};
}

/**
*
* @private
* @internal
*/
export function _downgradeInjectable( { token, injectable, downgradeFn }: ProvideNg2InjectableParams ): {name: string, factoryFn: Function, deps: string[]} {
const downgradedInjectableFactory = downgradeFn( injectable );
const { $inject = [] } = downgradedInjectableFactory;
const name = getInjectableName( token );

return {
name,
factoryFn: downgradedInjectableFactory,
deps: $inject
};
}
3 changes: 3 additions & 0 deletions src/upgrade/static/static.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { provideNg2Component, downgradeNg2Component } from './downgrade_component';
export { provideNg2Injectable, downgradeNg2Injectable } from './downgrade_injectable';
export { upgradeInjectable } from './upgrade_injectable';
Loading

0 comments on commit d0c0e5d

Please sign in to comment.