-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #180 from ngParty/upgrade/static
- Loading branch information
Showing
12 changed files
with
723 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'; |
Oops, something went wrong.