From 9aafdc7b02eea4ad39a3d5df661d809e8563f012 Mon Sep 17 00:00:00 2001 From: Georgios Kalpakas Date: Sat, 14 Jan 2017 00:36:16 +0200 Subject: [PATCH] feat(upgrade): allow non-element selectors for downgraded components (#14037) This affects the dynamic version of `upgrade` and makes it more consistent with the static version, while removing an artificial limitation. This commit also refactors the file layout and code, in order to share code wrt to dowgrading components between the dynamic and static versions. --- .../src/{static => common}/component_info.ts | 2 +- .../{static => common}/downgrade_component.ts | 5 +- .../downgrade_component_adapter.ts | 5 +- .../@angular/upgrade/src/dynamic/metadata.ts | 53 ++++--------------- .../upgrade/src/dynamic/upgrade_adapter.ts | 10 ++-- modules/@angular/upgrade/static.ts | 2 +- .../{static => common}/component_info_spec.ts | 2 +- .../upgrade/test/dynamic/metadata_spec.ts | 16 ++---- .../upgrade/test/dynamic/upgrade_spec.ts | 20 ++++++- .../integration/downgrade_component_spec.ts | 24 +++++++++ 10 files changed, 71 insertions(+), 68 deletions(-) rename modules/@angular/upgrade/src/{static => common}/component_info.ts (99%) rename modules/@angular/upgrade/src/{static => common}/downgrade_component.ts (98%) rename modules/@angular/upgrade/src/{static => common}/downgrade_component_adapter.ts (98%) rename modules/@angular/upgrade/test/{static => common}/component_info_spec.ts (96%) diff --git a/modules/@angular/upgrade/src/static/component_info.ts b/modules/@angular/upgrade/src/common/component_info.ts similarity index 99% rename from modules/@angular/upgrade/src/static/component_info.ts rename to modules/@angular/upgrade/src/common/component_info.ts index a9dc0c3a74..130f8f2589 100644 --- a/modules/@angular/upgrade/src/static/component_info.ts +++ b/modules/@angular/upgrade/src/common/component_info.ts @@ -44,4 +44,4 @@ export class PropertyBinding { this.bindAttr = `bind${capitalAttr}`; this.bindonAttr = `bindon${capitalAttr}`; } -} \ No newline at end of file +} diff --git a/modules/@angular/upgrade/src/static/downgrade_component.ts b/modules/@angular/upgrade/src/common/downgrade_component.ts similarity index 98% rename from modules/@angular/upgrade/src/static/downgrade_component.ts rename to modules/@angular/upgrade/src/common/downgrade_component.ts index c333688d15..7db8c71343 100644 --- a/modules/@angular/upgrade/src/static/downgrade_component.ts +++ b/modules/@angular/upgrade/src/common/downgrade_component.ts @@ -8,9 +8,8 @@ import {ComponentFactory, ComponentFactoryResolver, Injector, Type} from '@angular/core'; -import * as angular from '../common/angular1'; -import {$INJECTOR, $PARSE, INJECTOR_KEY, REQUIRE_NG_MODEL} from '../common/constants'; - +import * as angular from './angular1'; +import {$INJECTOR, $PARSE, INJECTOR_KEY, REQUIRE_NG_MODEL} from './constants'; import {DowngradeComponentAdapter} from './downgrade_component_adapter'; let downgradeCount = 0; diff --git a/modules/@angular/upgrade/src/static/downgrade_component_adapter.ts b/modules/@angular/upgrade/src/common/downgrade_component_adapter.ts similarity index 98% rename from modules/@angular/upgrade/src/static/downgrade_component_adapter.ts rename to modules/@angular/upgrade/src/common/downgrade_component_adapter.ts index e2c97df327..59987739fa 100644 --- a/modules/@angular/upgrade/src/static/downgrade_component_adapter.ts +++ b/modules/@angular/upgrade/src/common/downgrade_component_adapter.ts @@ -8,11 +8,10 @@ import {ChangeDetectorRef, ComponentFactory, ComponentRef, EventEmitter, Injector, OnChanges, ReflectiveInjector, SimpleChange, SimpleChanges, Type} from '@angular/core'; -import * as angular from '../common/angular1'; -import {$SCOPE} from '../common/constants'; +import * as angular from './angular1'; import {hookupNgModel} from '../common/util'; - import {ComponentInfo, PropertyBinding} from './component_info'; +import {$SCOPE} from './constants'; const INITIAL_VALUE = { __UNINITIALIZED__: true diff --git a/modules/@angular/upgrade/src/dynamic/metadata.ts b/modules/@angular/upgrade/src/dynamic/metadata.ts index 8de96b4b8a..e842051f55 100644 --- a/modules/@angular/upgrade/src/dynamic/metadata.ts +++ b/modules/@angular/upgrade/src/dynamic/metadata.ts @@ -9,63 +9,32 @@ import {DirectiveResolver} from '@angular/compiler'; import {Directive, Type} from '@angular/core'; +import {PropertyBinding} from '../common/component_info'; + + const COMPONENT_SELECTOR = /^[\w|-]*$/; const SKEWER_CASE = /-(\w)/g; const directiveResolver = new DirectiveResolver(); -export interface AttrProp { - prop: string; - attr: string; - bracketAttr: string; - bracketParenAttr: string; - parenAttr: string; - onAttr: string; - bindAttr: string; - bindonAttr: string; -} - export interface ComponentInfo { type: Type; selector: string; - inputs?: AttrProp[]; - outputs?: AttrProp[]; + inputs?: PropertyBinding[]; + outputs?: PropertyBinding[]; } export function getComponentInfo(type: Type): ComponentInfo { const resolvedMetadata: Directive = directiveResolver.resolve(type); - let selector = resolvedMetadata.selector; - if (!selector.match(COMPONENT_SELECTOR)) { - throw new Error('Only selectors matching element names are supported, got: ' + selector); - } - selector = selector.replace( - SKEWER_CASE, (all: any /** TODO #9100 */, letter: string) => letter.toUpperCase()); + const selector = resolvedMetadata.selector; + return { - type: type, - selector: selector, + type, + selector, inputs: parseFields(resolvedMetadata.inputs), outputs: parseFields(resolvedMetadata.outputs) }; } -export function parseFields(names: string[]): AttrProp[] { - const attrProps: AttrProp[] = []; - if (names) { - for (let i = 0; i < names.length; i++) { - const parts = names[i].split(':'); - const prop = parts[0].trim(); - const attr = (parts[1] || parts[0]).trim(); - const capitalAttr = attr.charAt(0).toUpperCase() + attr.substr(1); - attrProps.push({ - prop: prop, - attr: attr, - bracketAttr: `[${attr}]`, - parenAttr: `(${attr})`, - bracketParenAttr: `[(${attr})]`, - onAttr: `on${capitalAttr}`, - bindAttr: `bind${capitalAttr}`, - bindonAttr: `bindon${capitalAttr}` - }); - } - } - return attrProps; +export function parseFields(bindings: string[]): PropertyBinding[] { + return (bindings || []).map(binding => new PropertyBinding(binding)); } diff --git a/modules/@angular/upgrade/src/dynamic/upgrade_adapter.ts b/modules/@angular/upgrade/src/dynamic/upgrade_adapter.ts index fcee9d6998..5353641327 100644 --- a/modules/@angular/upgrade/src/dynamic/upgrade_adapter.ts +++ b/modules/@angular/upgrade/src/dynamic/upgrade_adapter.ts @@ -112,7 +112,7 @@ export class UpgradeAdapter { * @internal */ private ng1ComponentsToBeUpgraded: {[name: string]: UpgradeNg1ComponentAdapterBuilder} = {}; - private providers: Provider[] = []; + private upgradedProviders: Provider[] = []; private ngZone: NgZone; private ng1Module: angular.IModule; private moduleRef: NgModuleRef = null; @@ -437,11 +437,11 @@ export class UpgradeAdapter { * * ``` */ - public upgradeNg1Provider(name: string, options?: {asToken: any}) { + upgradeNg1Provider(name: string, options?: {asToken: any}) { const token = options && options.asToken || name; - this.providers.push({ + this.upgradedProviders.push({ provide: token, - useFactory: (ng1Injector: angular.IInjectorService) => ng1Injector.get(name), + useFactory: ($injector: angular.IInjectorService) => $injector.get(name), deps: [$INJECTOR] }); } @@ -557,7 +557,7 @@ export class UpgradeAdapter { providers: [ {provide: $INJECTOR, useFactory: () => ng1Injector}, {provide: $COMPILE, useFactory: () => ng1Injector.get($COMPILE)}, - this.providers + this.upgradedProviders ], imports: [this.ng2AppModule] }).Class({ diff --git a/modules/@angular/upgrade/static.ts b/modules/@angular/upgrade/static.ts index 1d2db00603..6a3760fbb0 100644 --- a/modules/@angular/upgrade/static.ts +++ b/modules/@angular/upgrade/static.ts @@ -12,8 +12,8 @@ * Entry point for all public APIs of the upgrade/static package, allowing * Angular 1 and Angular 2+ to run side by side in the same application. */ +export {downgradeComponent} from './src/common/downgrade_component'; export {downgradeInjectable} from './src/common/downgrade_injectable'; -export {downgradeComponent} from './src/static/downgrade_component'; export {UpgradeComponent} from './src/static/upgrade_component'; export {UpgradeModule} from './src/static/upgrade_module'; diff --git a/modules/@angular/upgrade/test/static/component_info_spec.ts b/modules/@angular/upgrade/test/common/component_info_spec.ts similarity index 96% rename from modules/@angular/upgrade/test/static/component_info_spec.ts rename to modules/@angular/upgrade/test/common/component_info_spec.ts index fc71925525..b4b13f2fe4 100644 --- a/modules/@angular/upgrade/test/static/component_info_spec.ts +++ b/modules/@angular/upgrade/test/common/component_info_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {PropertyBinding} from '@angular/upgrade/src/aot/component_info'; +import {PropertyBinding} from '@angular/upgrade/src/common/component_info'; export function main() { describe('PropertyBinding', () => { diff --git a/modules/@angular/upgrade/test/dynamic/metadata_spec.ts b/modules/@angular/upgrade/test/dynamic/metadata_spec.ts index 95c4b2c94d..5975d8b559 100644 --- a/modules/@angular/upgrade/test/dynamic/metadata_spec.ts +++ b/modules/@angular/upgrade/test/dynamic/metadata_spec.ts @@ -7,23 +7,17 @@ */ import {Component} from '@angular/core'; -import {describe, expect, it} from '@angular/core/testing/testing_internal'; import {getComponentInfo, parseFields} from '@angular/upgrade/src/dynamic/metadata'; export function main() { describe('upgrade metadata', () => { it('should extract component selector', () => { - expect(getComponentInfo(ElementNameComponent).selector).toEqual('elementNameDashed'); + expect(getComponentInfo(ElementNameComponent).selector).toBe('element-name-dashed'); }); describe('errors', () => { it('should throw on missing selector', () => { - expect(() => getComponentInfo(AttributeNameComponent)) - .toThrowError('Only selectors matching element names are supported, got: [attr-name]'); - }); - - it('should throw on non element names', () => { expect(() => getComponentInfo(NoAnnotationComponent)) .toThrowError('No Directive annotation found on NoAnnotationComponent'); }); @@ -34,7 +28,7 @@ export function main() { it('should process values', () => { expect(parseFields([' name ', ' prop : attr '])).toEqual([ - { + jasmine.objectContaining({ prop: 'name', attr: 'name', bracketAttr: '[name]', @@ -43,8 +37,8 @@ export function main() { onAttr: 'onName', bindAttr: 'bindName', bindonAttr: 'bindonName' - }, - { + }), + jasmine.objectContaining({ prop: 'prop', attr: 'attr', bracketAttr: '[attr]', @@ -53,7 +47,7 @@ export function main() { onAttr: 'onAttr', bindAttr: 'bindAttr', bindonAttr: 'bindonAttr' - } + }) ]); }); }); diff --git a/modules/@angular/upgrade/test/dynamic/upgrade_spec.ts b/modules/@angular/upgrade/test/dynamic/upgrade_spec.ts index 157be74c7a..a5e66adaf4 100644 --- a/modules/@angular/upgrade/test/dynamic/upgrade_spec.ts +++ b/modules/@angular/upgrade/test/dynamic/upgrade_spec.ts @@ -271,6 +271,25 @@ export function main() { }); describe('downgrade ng2 component', () => { + it('should allow non-element selectors for downgraded components', async(() => { + @Component({selector: '[itWorks]', template: 'It works'}) + class WorksComponent { + } + + @NgModule({declarations: [WorksComponent], imports: [BrowserModule]}) + class Ng2Module { + } + + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const ng1Module = angular.module('ng1', []); + ng1Module.directive('ng2', adapter.downgradeNg2Component(WorksComponent)); + + const element = html(''); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + expect(multiTrim(document.body.textContent)).toBe('It works'); + }); + })); + it('should bind properties, events', async(() => { const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); const ng1Module = angular.module('ng1', []); @@ -458,7 +477,6 @@ export function main() { }); })); - it('should fallback to the root ng2.injector when compiled outside the dom', async(() => { const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); const ng1Module = angular.module('ng1', []); diff --git a/modules/@angular/upgrade/test/static/integration/downgrade_component_spec.ts b/modules/@angular/upgrade/test/static/integration/downgrade_component_spec.ts index 3670228b63..3bc42020e3 100644 --- a/modules/@angular/upgrade/test/static/integration/downgrade_component_spec.ts +++ b/modules/@angular/upgrade/test/static/integration/downgrade_component_spec.ts @@ -273,6 +273,30 @@ export function main() { }); })); + it('should allow attribute selectors for downgraded components', async(() => { + @Component({selector: '[itWorks]', template: 'It works'}) + class WorksComponent { + } + + @NgModule({ + declarations: [WorksComponent], + entryComponents: [WorksComponent], + imports: [BrowserModule, UpgradeModule] + }) + class Ng2Module { + ngDoBootstrap() {} + } + + const ng1Module = angular.module('ng1', []).directive( + 'worksComponent', downgradeComponent({component: WorksComponent})); + + const element = html(''); + + bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { + expect(multiTrim(document.body.textContent)).toBe('It works'); + }); + })); + it('should allow attribute selectors for components in ng2', async(() => { @Component({selector: '[itWorks]', template: 'It works'}) class WorksComponent {