Revert "feat(upgrade): use `ComponentFactory.inputs/outputs/ngContentSelectors`"

This reverts commit a3e32fb7e1.
This commit is contained in:
Chuck Jazdzewski 2017-03-15 13:22:54 -07:00
parent a3e32fb7e1
commit c439742a54
22 changed files with 418 additions and 94 deletions

View File

@ -156,6 +156,7 @@ export class CompileMetadataResolver {
const templateName = inputs[propName]; const templateName = inputs[propName];
factory.inputs.push({propName, templateName}); factory.inputs.push({propName, templateName});
} }
const outputsArr: {propName: string, templateName: string}[] = [];
for (let propName in outputs) { for (let propName in outputs) {
const templateName = outputs[propName]; const templateName = outputs[propName];
factory.outputs.push({propName, templateName}); factory.outputs.push({propName, templateName});

View File

@ -279,6 +279,7 @@ describe('compiler (unbundled Angular)', () => {
const host = new MockCompilerHost(['/app/app.ts'], FILES, angularFiles); const host = new MockCompilerHost(['/app/app.ts'], FILES, angularFiles);
const aotHost = new MockAotCompilerHost(host); const aotHost = new MockAotCompilerHost(host);
let generatedFiles: GeneratedFile[]; let generatedFiles: GeneratedFile[];
const warnSpy = spyOn(console, 'warn');
compile(host, aotHost, expectNoDiagnostics).then((f) => generatedFiles = f); compile(host, aotHost, expectNoDiagnostics).then((f) => generatedFiles = f);
tick(); tick();

View File

@ -145,7 +145,12 @@ ng1AppModule.factory('heroesService', downgradeInjectable(HeroesService));
// #docregion ng2-heroes-wrapper // #docregion ng2-heroes-wrapper
// This is directive will act as the interface to the "downgraded" Angular component // This is directive will act as the interface to the "downgraded" Angular component
ng1AppModule.directive('ng2Heroes', downgradeComponent({component: Ng2HeroesComponent})); ng1AppModule.directive(
'ng2Heroes',
downgradeComponent(
// The inputs and outputs here must match the relevant names of the properties on the
// "downgraded" component
{component: Ng2HeroesComponent, inputs: ['heroes'], outputs: ['addHero', 'removeHero']}));
// #enddocregion // #enddocregion
// #docregion example-app // #docregion example-app

View File

@ -6,6 +6,15 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Type} from '@angular/core';
export interface ComponentInfo {
component: Type<any>;
inputs?: string[];
outputs?: string[];
selectors?: string[];
}
/** /**
* A `PropertyBinding` represents a mapping between a property name * A `PropertyBinding` represents a mapping between a property name
* and an attribute name. It is parsed from a string of the form * and an attribute name. It is parsed from a string of the form
@ -13,6 +22,8 @@
* and attribute have the same identifier. * and attribute have the same identifier.
*/ */
export class PropertyBinding { export class PropertyBinding {
prop: string;
attr: string;
bracketAttr: string; bracketAttr: string;
bracketParenAttr: string; bracketParenAttr: string;
parenAttr: string; parenAttr: string;
@ -20,9 +31,12 @@ export class PropertyBinding {
bindAttr: string; bindAttr: string;
bindonAttr: string; bindonAttr: string;
constructor(public prop: string, public attr: string) { this.parseBinding(); } constructor(public binding: string) { this.parseBinding(); }
private parseBinding() { private parseBinding() {
const parts = this.binding.split(':');
this.prop = parts[0].trim();
this.attr = (parts[1] || this.prop).trim();
this.bracketAttr = `[${this.attr}]`; this.bracketAttr = `[${this.attr}]`;
this.parenAttr = `(${this.attr})`; this.parenAttr = `(${this.attr})`;
this.bracketParenAttr = `[(${this.attr})]`; this.bracketParenAttr = `[(${this.attr})]`;

View File

@ -0,0 +1,20 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Type} from '@angular/core';
import * as angular from './angular1';
export class ContentProjectionHelper {
groupProjectableNodes($injector: angular.IInjectorService, component: Type<any>, nodes: Node[]):
Node[][] {
// By default, do not support multi-slot projection,
// as `upgrade/static` does not support it yet.
return [nodes];
}
}

View File

@ -11,6 +11,7 @@ import {ComponentFactory, ComponentFactoryResolver, Injector, Type} from '@angul
import * as angular from './angular1'; import * as angular from './angular1';
import {$COMPILE, $INJECTOR, $PARSE, INJECTOR_KEY, REQUIRE_INJECTOR, REQUIRE_NG_MODEL} from './constants'; import {$COMPILE, $INJECTOR, $PARSE, INJECTOR_KEY, REQUIRE_INJECTOR, REQUIRE_NG_MODEL} from './constants';
import {DowngradeComponentAdapter} from './downgrade_component_adapter'; import {DowngradeComponentAdapter} from './downgrade_component_adapter';
import {NgContentSelectorHelper} from './ng_content_selector_helper';
import {controllerKey, getComponentName} from './util'; import {controllerKey, getComponentName} from './util';
let downgradeCount = 0; let downgradeCount = 0;
@ -37,6 +38,15 @@ let downgradeCount = 0;
* *
* {@example upgrade/static/ts/module.ts region="ng2-heroes-wrapper"} * {@example upgrade/static/ts/module.ts region="ng2-heroes-wrapper"}
* *
* In this example you can see that we must provide information about the component being
* "downgraded". This is because once the AoT compiler has run, all metadata about the
* component has been removed from the code, and so cannot be inferred.
*
* We must do the following:
* * specify the Angular component class that is to be downgraded
* * specify all inputs and outputs that the AngularJS component expects
* * specify the selectors used in any `ng-content` elements in the component's template
*
* @description * @description
* *
* A helper function that returns a factory function to be used for registering an * A helper function that returns a factory function to be used for registering an
@ -45,17 +55,28 @@ let downgradeCount = 0;
* The parameter contains information about the Component that is being downgraded: * The parameter contains information about the Component that is being downgraded:
* *
* * `component: Type<any>`: The type of the Component that will be downgraded * * `component: Type<any>`: The type of the Component that will be downgraded
* * `inputs: string[]`: A collection of strings that specify what inputs the component accepts
* * `outputs: string[]`: A collection of strings that specify what outputs the component emits
* * `selectors: string[]`: A collection of strings that specify what selectors are expected on
* `ng-content` elements in the template to enable content projection (a.k.a. transclusion in
* AngularJS)
*
* The `inputs` and `outputs` are strings that map the names of properties to camelCased
* attribute names. They are of the form `"prop: attr"`; or simply `"propAndAttr" where the
* property and attribute have the same identifier.
*
* The `selectors` are the values of the `select` attribute of each of the `ng-content` elements
* that appear in the downgraded component's template.
* These selectors must be provided in the order that they appear in the template as they are
* mapped by index to the projected nodes.
* *
* @experimental * @experimental
*/ */
export function downgradeComponent(info: { export function downgradeComponent(info: /* ComponentInfo */ {
component: Type<any>; component: Type<any>;
/** @deprecated since v4. This parameter is no longer used */
inputs?: string[]; inputs?: string[];
/** @deprecated since v4. This parameter is no longer used */
outputs?: string[]; outputs?: string[];
/** @deprecated since v4. This parameter is no longer used */ selectors?: string[]
selectors?: string[];
}): any /* angular.IInjectable */ { }): any /* angular.IInjectable */ {
const idPrefix = `NG2_UPGRADE_${downgradeCount++}_`; const idPrefix = `NG2_UPGRADE_${downgradeCount++}_`;
let idCount = 0; let idCount = 0;
@ -93,7 +114,7 @@ export function downgradeComponent(info: {
const id = idPrefix + (idCount++); const id = idPrefix + (idCount++);
const injectorPromise = new ParentInjectorPromise(element); const injectorPromise = new ParentInjectorPromise(element);
const facade = new DowngradeComponentAdapter( const facade = new DowngradeComponentAdapter(
id, element, attrs, scope, ngModel, injector, $injector, $compile, $parse, id, info, element, attrs, scope, ngModel, injector, $injector, $compile, $parse,
componentFactory); componentFactory);
const projectableNodes = facade.compileContents(); const projectableNodes = facade.compileContents();

View File

@ -9,8 +9,9 @@
import {ChangeDetectorRef, ComponentFactory, ComponentRef, EventEmitter, Injector, OnChanges, ReflectiveInjector, SimpleChange, SimpleChanges, Type} from '@angular/core'; import {ChangeDetectorRef, ComponentFactory, ComponentRef, EventEmitter, Injector, OnChanges, ReflectiveInjector, SimpleChange, SimpleChanges, Type} from '@angular/core';
import * as angular from './angular1'; import * as angular from './angular1';
import {PropertyBinding} from './component_info'; import {ComponentInfo, PropertyBinding} from './component_info';
import {$SCOPE} from './constants'; import {$SCOPE} from './constants';
import {NgContentSelectorHelper} from './ng_content_selector_helper';
import {getAttributesAsArray, getComponentName, hookupNgModel} from './util'; import {getAttributesAsArray, getComponentName, hookupNgModel} from './util';
const INITIAL_VALUE = { const INITIAL_VALUE = {
@ -26,7 +27,7 @@ export class DowngradeComponentAdapter {
private changeDetector: ChangeDetectorRef = null; private changeDetector: ChangeDetectorRef = null;
constructor( constructor(
private id: string, private element: angular.IAugmentedJQuery, private id: string, private info: ComponentInfo, private element: angular.IAugmentedJQuery,
private attrs: angular.IAttributes, private scope: angular.IScope, private attrs: angular.IAttributes, private scope: angular.IScope,
private ngModel: angular.INgModelController, private parentInjector: Injector, private ngModel: angular.INgModelController, private parentInjector: Injector,
private $injector: angular.IInjectorService, private $compile: angular.ICompileService, private $injector: angular.IInjectorService, private $compile: angular.ICompileService,
@ -66,9 +67,9 @@ export class DowngradeComponentAdapter {
setupInputs(): void { setupInputs(): void {
const attrs = this.attrs; const attrs = this.attrs;
const inputs = this.componentFactory.inputs || []; const inputs = this.info.inputs || [];
for (let i = 0; i < inputs.length; i++) { for (let i = 0; i < inputs.length; i++) {
const input = new PropertyBinding(inputs[i].propName, inputs[i].templateName); const input = new PropertyBinding(inputs[i]);
let expr: any /** TODO #9100 */ = null; let expr: any /** TODO #9100 */ = null;
if (attrs.hasOwnProperty(input.attr)) { if (attrs.hasOwnProperty(input.attr)) {
@ -102,7 +103,7 @@ export class DowngradeComponentAdapter {
} }
} }
const prototype = this.componentFactory.componentType.prototype; const prototype = this.info.component.prototype;
if (prototype && (<OnChanges>prototype).ngOnChanges) { if (prototype && (<OnChanges>prototype).ngOnChanges) {
// Detect: OnChanges interface // Detect: OnChanges interface
this.inputChanges = {}; this.inputChanges = {};
@ -117,9 +118,9 @@ export class DowngradeComponentAdapter {
setupOutputs() { setupOutputs() {
const attrs = this.attrs; const attrs = this.attrs;
const outputs = this.componentFactory.outputs || []; const outputs = this.info.outputs || [];
for (let j = 0; j < outputs.length; j++) { for (let j = 0; j < outputs.length; j++) {
const output = new PropertyBinding(outputs[j].propName, outputs[j].templateName); const output = new PropertyBinding(outputs[j]);
let expr: any /** TODO #9100 */ = null; let expr: any /** TODO #9100 */ = null;
let assignExpr = false; let assignExpr = false;
@ -157,7 +158,7 @@ export class DowngradeComponentAdapter {
}); });
} else { } else {
throw new Error( throw new Error(
`Missing emitter '${output.prop}' on component '${getComponentName(this.componentFactory.componentType)}'!`); `Missing emitter '${output.prop}' on component '${getComponentName(this.info.component)}'!`);
} }
} }
} }
@ -182,15 +183,21 @@ export class DowngradeComponentAdapter {
} }
groupProjectableNodes() { groupProjectableNodes() {
let ngContentSelectors = this.componentFactory.ngContentSelectors; const ngContentSelectorHelper =
return groupNodesBySelector(ngContentSelectors, this.element.contents()); this.parentInjector.get(NgContentSelectorHelper) as NgContentSelectorHelper;
} const ngContentSelectors = ngContentSelectorHelper.getNgContentSelectors(this.info);
}
/** if (!ngContentSelectors) {
throw new Error('Expecting ngContentSelectors for: ' + getComponentName(this.info.component));
}
return this._groupNodesBySelector(ngContentSelectors, this.element.contents());
}
/**
* Group a set of DOM nodes into `ngContent` groups, based on the given content selectors. * Group a set of DOM nodes into `ngContent` groups, based on the given content selectors.
*/ */
export function groupNodesBySelector(ngContentSelectors: string[], nodes: Node[]): Node[][] { private _groupNodesBySelector(ngContentSelectors: string[], nodes: Node[]): Node[][] {
const projectableNodes: Node[][] = []; const projectableNodes: Node[][] = [];
let wildcardNgContentIndex: number; let wildcardNgContentIndex: number;
@ -207,6 +214,18 @@ export function groupNodesBySelector(ngContentSelectors: string[], nodes: Node[]
} }
return projectableNodes; return projectableNodes;
}
}
let _matches: (this: any, selector: string) => boolean;
function matchesSelector(el: any, selector: string): boolean {
if (!_matches) {
const elProto = <any>Element.prototype;
_matches = elProto.matchesSelector || elProto.mozMatchesSelector || elProto.msMatchesSelector ||
elProto.oMatchesSelector || elProto.webkitMatchesSelector;
}
return _matches.call(el, selector);
} }
function findMatchingNgContentIndex(element: any, ngContentSelectors: string[]): number { function findMatchingNgContentIndex(element: any, ngContentSelectors: string[]): number {
@ -229,14 +248,3 @@ function findMatchingNgContentIndex(element: any, ngContentSelectors: string[]):
} }
return ngContentIndices.length ? ngContentIndices[0] : null; return ngContentIndices.length ? ngContentIndices[0] : null;
} }
let _matches: (this: any, selector: string) => boolean;
function matchesSelector(el: any, selector: string): boolean {
if (!_matches) {
const elProto = <any>Element.prototype;
_matches = elProto.matches || elProto.matchesSelector || elProto.mozMatchesSelector ||
elProto.msMatchesSelector || elProto.oMatchesSelector || elProto.webkitMatchesSelector;
}
return el.nodeType === Node.ELEMENT_NODE ? _matches.call(el, selector) : false;
}

View File

@ -0,0 +1,24 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {ComponentInfo} from './component_info';
/**
* This class gives an extension point between the static and dynamic versions
* of ngUpgrade:
* * In the static version (this one) we must specify them manually as part of
* the call to `downgradeComponent(...)`.
* * In the dynamic version (`DynamicNgContentSelectorHelper`) we are able to
* ask the compiler for the selectors of a component.
*/
export class NgContentSelectorHelper {
getNgContentSelectors(info: ComponentInfo): string[] {
// if no selectors are passed then default to a single "wildcard" selector
return info.selectors || ['*'];
}
}

View File

@ -0,0 +1,70 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {CssSelector, SelectorMatcher, createElementCssSelector} from '@angular/compiler';
import {Compiler, Type} from '@angular/core';
import * as angular from '../common/angular1';
import {COMPILER_KEY} from '../common/constants';
import {ContentProjectionHelper} from '../common/content_projection_helper';
import {getAttributesAsArray, getComponentName} from '../common/util';
export class DynamicContentProjectionHelper extends ContentProjectionHelper {
groupProjectableNodes($injector: angular.IInjectorService, component: Type<any>, nodes: Node[]):
Node[][] {
const ng2Compiler = $injector.get(COMPILER_KEY) as Compiler;
const ngContentSelectors = ng2Compiler.getNgContentSelectors(component);
if (!ngContentSelectors) {
throw new Error('Expecting ngContentSelectors for: ' + getComponentName(component));
}
return this.groupNodesBySelector(ngContentSelectors, nodes);
}
/**
* Group a set of DOM nodes into `ngContent` groups, based on the given content selectors.
*/
groupNodesBySelector(ngContentSelectors: string[], nodes: Node[]): Node[][] {
const projectableNodes: Node[][] = [];
let matcher = new SelectorMatcher();
let wildcardNgContentIndex: number;
for (let i = 0, ii = ngContentSelectors.length; i < ii; ++i) {
projectableNodes[i] = [];
const selector = ngContentSelectors[i];
if (selector === '*') {
wildcardNgContentIndex = i;
} else {
matcher.addSelectables(CssSelector.parse(selector), i);
}
}
for (let j = 0, jj = nodes.length; j < jj; ++j) {
const ngContentIndices: number[] = [];
const node = nodes[j];
const selector =
createElementCssSelector(node.nodeName.toLowerCase(), getAttributesAsArray(node));
matcher.match(selector, (_, index) => ngContentIndices.push(index));
ngContentIndices.sort();
if (wildcardNgContentIndex !== undefined) {
ngContentIndices.push(wildcardNgContentIndex);
}
if (ngContentIndices.length) {
projectableNodes[ngContentIndices[0]].push(node);
}
}
return projectableNodes;
}
}

View File

@ -0,0 +1,24 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Compiler, Injectable} from '@angular/core';
import {ComponentInfo} from '../common/component_info';
import {NgContentSelectorHelper} from '../common/ng_content_selector_helper';
/**
* See `NgContentSelectorHelper` for more information about this class.
*/
@Injectable()
export class DynamicNgContentSelectorHelper extends NgContentSelectorHelper {
constructor(private compiler: Compiler) { super(); }
getNgContentSelectors(info: ComponentInfo): string[] {
return this.compiler.getNgContentSelectors(info.component);
}
}

View File

@ -6,15 +6,19 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {DirectiveResolver} from '@angular/compiler';
import {Compiler, CompilerOptions, Directive, Injector, NgModule, NgModuleRef, NgZone, Provider, Testability, Type} from '@angular/core'; import {Compiler, CompilerOptions, Directive, Injector, NgModule, NgModuleRef, NgZone, Provider, Testability, Type} from '@angular/core';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import * as angular from '../common/angular1'; import * as angular from '../common/angular1';
import {ComponentInfo} from '../common/component_info';
import {$$TESTABILITY, $COMPILE, $INJECTOR, $ROOT_SCOPE, COMPILER_KEY, INJECTOR_KEY, NG_ZONE_KEY} from '../common/constants'; import {$$TESTABILITY, $COMPILE, $INJECTOR, $ROOT_SCOPE, COMPILER_KEY, INJECTOR_KEY, NG_ZONE_KEY} from '../common/constants';
import {downgradeComponent} from '../common/downgrade_component'; import {downgradeComponent} from '../common/downgrade_component';
import {downgradeInjectable} from '../common/downgrade_injectable'; import {downgradeInjectable} from '../common/downgrade_injectable';
import {NgContentSelectorHelper} from '../common/ng_content_selector_helper';
import {Deferred, controllerKey, onError} from '../common/util'; import {Deferred, controllerKey, onError} from '../common/util';
import {DynamicNgContentSelectorHelper} from './ng_content_selector_helper';
import {UpgradeNg1ComponentAdapterBuilder} from './upgrade_ng1_adapter'; import {UpgradeNg1ComponentAdapterBuilder} from './upgrade_ng1_adapter';
let upgradeCount: number = 0; let upgradeCount: number = 0;
@ -100,6 +104,7 @@ let upgradeCount: number = 0;
*/ */
export class UpgradeAdapter { export class UpgradeAdapter {
private idPrefix: string = `NG2_UPGRADE_${upgradeCount++}_`; private idPrefix: string = `NG2_UPGRADE_${upgradeCount++}_`;
private directiveResolver: DirectiveResolver = new DirectiveResolver();
private downgradedComponents: Type<any>[] = []; private downgradedComponents: Type<any>[] = [];
/** /**
* An internal map of ng1 components which need to up upgraded to ng2. * An internal map of ng1 components which need to up upgraded to ng2.
@ -185,7 +190,10 @@ export class UpgradeAdapter {
downgradeNg2Component(component: Type<any>): Function { downgradeNg2Component(component: Type<any>): Function {
this.downgradedComponents.push(component); this.downgradedComponents.push(component);
return downgradeComponent({component}); const metadata: Directive = this.directiveResolver.resolve(component);
const info: ComponentInfo = {component, inputs: metadata.inputs, outputs: metadata.outputs};
return downgradeComponent(info);
} }
/** /**
@ -553,6 +561,7 @@ export class UpgradeAdapter {
providers: [ providers: [
{provide: $INJECTOR, useFactory: () => ng1Injector}, {provide: $INJECTOR, useFactory: () => ng1Injector},
{provide: $COMPILE, useFactory: () => ng1Injector.get($COMPILE)}, {provide: $COMPILE, useFactory: () => ng1Injector.get($COMPILE)},
{provide: NgContentSelectorHelper, useClass: DynamicNgContentSelectorHelper},
this.upgradedProviders this.upgradedProviders
], ],
imports: [this.ng2AppModule], imports: [this.ng2AppModule],

View File

@ -10,6 +10,7 @@ import {Injector, NgModule, NgZone, Testability} from '@angular/core';
import * as angular from '../common/angular1'; import * as angular from '../common/angular1';
import {$$TESTABILITY, $DELEGATE, $INJECTOR, $PROVIDE, $ROOT_SCOPE, INJECTOR_KEY, UPGRADE_MODULE_NAME} from '../common/constants'; import {$$TESTABILITY, $DELEGATE, $INJECTOR, $PROVIDE, $ROOT_SCOPE, INJECTOR_KEY, UPGRADE_MODULE_NAME} from '../common/constants';
import {NgContentSelectorHelper} from '../common/ng_content_selector_helper';
import {controllerKey} from '../common/util'; import {controllerKey} from '../common/util';
import {angular1Providers, setTempInjectorRef} from './angular1_providers'; import {angular1Providers, setTempInjectorRef} from './angular1_providers';
@ -129,7 +130,7 @@ import {angular1Providers, setTempInjectorRef} from './angular1_providers';
* *
* @experimental * @experimental
*/ */
@NgModule({providers: [angular1Providers]}) @NgModule({providers: [angular1Providers, NgContentSelectorHelper]})
export class UpgradeModule { export class UpgradeModule {
/** /**
* The AngularJS `$injector` for the upgrade application. * The AngularJS `$injector` for the upgrade application.

View File

@ -11,7 +11,8 @@ import {PropertyBinding} from '@angular/upgrade/src/common/component_info';
export function main() { export function main() {
describe('PropertyBinding', () => { describe('PropertyBinding', () => {
it('should process a simple binding', () => { it('should process a simple binding', () => {
const binding = new PropertyBinding('someBinding', 'someBinding'); const binding = new PropertyBinding('someBinding');
expect(binding.binding).toEqual('someBinding');
expect(binding.prop).toEqual('someBinding'); expect(binding.prop).toEqual('someBinding');
expect(binding.attr).toEqual('someBinding'); expect(binding.attr).toEqual('someBinding');
expect(binding.bracketAttr).toEqual('[someBinding]'); expect(binding.bracketAttr).toEqual('[someBinding]');
@ -23,7 +24,21 @@ export function main() {
}); });
it('should process a two-part binding', () => { it('should process a two-part binding', () => {
const binding = new PropertyBinding('someProp', 'someAttr'); const binding = new PropertyBinding('someProp:someAttr');
expect(binding.binding).toEqual('someProp:someAttr');
expect(binding.prop).toEqual('someProp');
expect(binding.attr).toEqual('someAttr');
expect(binding.bracketAttr).toEqual('[someAttr]');
expect(binding.bracketParenAttr).toEqual('[(someAttr)]');
expect(binding.parenAttr).toEqual('(someAttr)');
expect(binding.onAttr).toEqual('onSomeAttr');
expect(binding.bindAttr).toEqual('bindSomeAttr');
expect(binding.bindonAttr).toEqual('bindonSomeAttr');
});
it('should cope with whitespace', () => {
const binding = new PropertyBinding(' someProp : someAttr ');
expect(binding.binding).toEqual(' someProp : someAttr ');
expect(binding.prop).toEqual('someProp'); expect(binding.prop).toEqual('someProp');
expect(binding.attr).toEqual('someAttr'); expect(binding.attr).toEqual('someAttr');
expect(binding.bracketAttr).toEqual('[someAttr]'); expect(binding.bracketAttr).toEqual('[someAttr]');

View File

@ -6,13 +6,25 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import * as angular from '@angular/upgrade/src/common/angular1'; import * as angular from '@angular/upgrade/src/common/angular1';
import {groupNodesBySelector} from '@angular/upgrade/src/common/downgrade_component_adapter'; import {DowngradeComponentAdapter} from '@angular/upgrade/src/common/downgrade_component_adapter';
import {NgContentSelectorHelper} from '@angular/upgrade/src/common/ng_content_selector_helper';
import {nodes} from './test_helpers'; import {nodes} from './test_helpers';
export function main() { export function main() {
describe('DowngradeComponentAdapter', () => { describe('DowngradeComponentAdapter', () => {
describe('groupNodesBySelector', () => { describe('groupNodesBySelector', () => {
function createAdapter(selectors: string[], contentNodes: Node[]): DowngradeComponentAdapter {
const selectorHelper = new NgContentSelectorHelper();
const fakeInjector = {get: function() { return selectorHelper; }};
const fakeScope = { $new: function() {} } as any;
const element = angular.element('<div></div>');
element.append(contentNodes);
return new DowngradeComponentAdapter(
'id', {component: null, selectors}, element, null, fakeScope, null, fakeInjector, null,
null, null, null);
}
it('should return an array of node collections for each selector', () => { it('should return an array of node collections for each selector', () => {
const contentNodes = nodes( const contentNodes = nodes(
'<div class="x"><span>div-1 content</span></div>' + '<div class="x"><span>div-1 content</span></div>' +
@ -22,7 +34,8 @@ export function main() {
'<div class="x"><span>div-2 content</span></div>'); '<div class="x"><span>div-2 content</span></div>');
const selectors = ['input[type=date]', 'span', '.x']; const selectors = ['input[type=date]', 'span', '.x'];
const projectableNodes = groupNodesBySelector(selectors, contentNodes); const adapter = createAdapter(selectors, contentNodes);
const projectableNodes = adapter.groupProjectableNodes();
expect(projectableNodes[0]).toEqual(nodes('<input type="date" name="myDate">')); expect(projectableNodes[0]).toEqual(nodes('<input type="date" name="myDate">'));
expect(projectableNodes[1]).toEqual(nodes('<span>span content</span>')); expect(projectableNodes[1]).toEqual(nodes('<span>span content</span>'));
@ -41,7 +54,8 @@ export function main() {
'<div class="x"><span>div-2 content</span></div>'); '<div class="x"><span>div-2 content</span></div>');
const selectors = ['.x', '*', 'input[type=date]']; const selectors = ['.x', '*', 'input[type=date]'];
const projectableNodes = groupNodesBySelector(selectors, contentNodes); const adapter = createAdapter(selectors, contentNodes);
const projectableNodes = adapter.groupProjectableNodes();
expect(projectableNodes[0]) expect(projectableNodes[0])
.toEqual(nodes( .toEqual(nodes(
@ -56,7 +70,8 @@ export function main() {
it('should return an array of empty arrays if there are no nodes passed in', () => { it('should return an array of empty arrays if there are no nodes passed in', () => {
const selectors = ['.x', '*', 'input[type=date]']; const selectors = ['.x', '*', 'input[type=date]'];
const projectableNodes = groupNodesBySelector(selectors, []); const adapter = createAdapter(selectors, []);
const projectableNodes = adapter.groupProjectableNodes();
expect(projectableNodes).toEqual([[], [], []]); expect(projectableNodes).toEqual([[], [], []]);
}); });
@ -68,10 +83,12 @@ export function main() {
'<span>span content</span>' + '<span>span content</span>' +
'<div class="x"><span>div-2 content</span></div>'); '<div class="x"><span>div-2 content</span></div>');
const projectableNodes = groupNodesBySelector([], contentNodes); const adapter1 = createAdapter([], contentNodes);
const projectableNodes = adapter1.groupProjectableNodes();
expect(projectableNodes).toEqual([]); expect(projectableNodes).toEqual([]);
const noMatchSelectorNodes = groupNodesBySelector(['.not-there'], contentNodes); const adapter2 = createAdapter(['.not-there'], contentNodes);
const noMatchSelectorNodes = adapter2.groupProjectableNodes();
expect(noMatchSelectorNodes).toEqual([[]]); expect(noMatchSelectorNodes).toEqual([[]]);
}); });
}); });

View File

@ -0,0 +1,85 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {DynamicContentProjectionHelper} from '@angular/upgrade/src/dynamic/content_projection_helper';
import {nodes} from './test_helpers';
export function main() {
describe('groupNodesBySelector', () => {
let groupNodesBySelector: (ngContentSelectors: string[], nodes: Node[]) => Node[][];
beforeEach(() => {
const projectionHelper = new DynamicContentProjectionHelper();
groupNodesBySelector = projectionHelper.groupNodesBySelector.bind(projectionHelper);
});
it('should return an array of node collections for each selector', () => {
const contentNodes = nodes(
'<div class="x"><span>div-1 content</span></div>' +
'<input type="number" name="myNum">' +
'<input type="date" name="myDate">' +
'<span>span content</span>' +
'<div class="x"><span>div-2 content</span></div>');
const selectors = ['input[type=date]', 'span', '.x'];
const projectableNodes = groupNodesBySelector(selectors, contentNodes);
expect(projectableNodes[0]).toEqual(nodes('<input type="date" name="myDate">'));
expect(projectableNodes[1]).toEqual(nodes('<span>span content</span>'));
expect(projectableNodes[2])
.toEqual(nodes(
'<div class="x"><span>div-1 content</span></div>' +
'<div class="x"><span>div-2 content</span></div>'));
});
it('should collect up unmatched nodes for the wildcard selector', () => {
const contentNodes = nodes(
'<div class="x"><span>div-1 content</span></div>' +
'<input type="number" name="myNum">' +
'<input type="date" name="myDate">' +
'<span>span content</span>' +
'<div class="x"><span>div-2 content</span></div>');
const selectors = ['.x', '*', 'input[type=date]'];
const projectableNodes = groupNodesBySelector(selectors, contentNodes);
expect(projectableNodes[0])
.toEqual(nodes(
'<div class="x"><span>div-1 content</span></div>' +
'<div class="x"><span>div-2 content</span></div>'));
expect(projectableNodes[1])
.toEqual(nodes(
'<input type="number" name="myNum">' +
'<span>span content</span>'));
expect(projectableNodes[2]).toEqual(nodes('<input type="date" name="myDate">'));
});
it('should return an array of empty arrays if there are no nodes passed in', () => {
const selectors = ['.x', '*', 'input[type=date]'];
const projectableNodes = groupNodesBySelector(selectors, []);
expect(projectableNodes).toEqual([[], [], []]);
});
it('should return an empty array for each selector that does not match', () => {
const contentNodes = nodes(
'<div class="x"><span>div-1 content</span></div>' +
'<input type="number" name="myNum">' +
'<input type="date" name="myDate">' +
'<span>span content</span>' +
'<div class="x"><span>div-2 content</span></div>');
const noSelectorNodes = groupNodesBySelector([], contentNodes);
expect(noSelectorNodes).toEqual([]);
const noMatchSelectorNodes = groupNodesBySelector(['.not-there'], contentNodes);
expect(noMatchSelectorNodes).toEqual([[]]);
});
});
}

View File

@ -73,7 +73,7 @@ export function main() {
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
expect(document.body.textContent).toEqual('1A;2A;ng1a;2B;ng1b;2C;1C;'); expect(document.body.textContent).toEqual('1A;2A;ng1a;2B;ng1b;2C;1C;');
// https://github.com/angular/angular.js/issues/12983 // https://github.com/angular/angular.js/issues/12983
expect(log).toEqual(['1A', '1C', '2A', '2B', '2C', 'ng1a', 'ng1b']); expect(log).toEqual(['1A', '1B', '1C', '2A', '2B', '2C', 'ng1a', 'ng1b']);
}); });
})); }));

View File

@ -72,8 +72,10 @@ export function main() {
ngDoBootstrap() {} ngDoBootstrap() {}
} }
const ng1Module = angular.module('ng1', []) const ng1Module =
.directive('ng2', downgradeComponent({component: Ng2Component})) angular.module('ng1', [])
.directive(
'ng2', downgradeComponent({component: Ng2Component, inputs: ['itemId']}))
.run(($rootScope: angular.IRootScopeService) => { .run(($rootScope: angular.IRootScopeService) => {
$rootScope['items'] = [ $rootScope['items'] = [
{id: 'a', subitems: [1, 2, 3]}, {id: 'b', subitems: [4, 5, 6]}, {id: 'a', subitems: [1, 2, 3]}, {id: 'b', subitems: [4, 5, 6]},
@ -160,7 +162,7 @@ export function main() {
} }
const ng1Module = angular.module('ng1', []).directive( const ng1Module = angular.module('ng1', []).directive(
'ng2', downgradeComponent({component: Ng2Component})); 'ng2', downgradeComponent({component: Ng2Component, selectors: ['.ng1a', '.ng1b']}));
// The ng-if on one of the projected children is here to make sure // The ng-if on one of the projected children is here to make sure
// the correct slot is targeted even with structural directives in play. // the correct slot is targeted even with structural directives in play.

View File

@ -108,8 +108,14 @@ export function main() {
}; };
} }
ng1Module.directive('ng2', downgradeComponent({ ng1Module.directive(
'ng2', downgradeComponent({
component: Ng2Component, component: Ng2Component,
inputs: ['literal', 'interpolate', 'oneWayA', 'oneWayB', 'twoWayA', 'twoWayB'],
outputs: [
'eventA', 'eventB', 'twoWayAEmitter: twoWayAChange',
'twoWayBEmitter: twoWayBChange'
]
})); }));
@NgModule({ @NgModule({

View File

@ -71,7 +71,9 @@ export function main() {
}; };
}) })
// This is wrapping (downgrading) an Angular component to be used in AngularJS // This is wrapping (downgrading) an Angular component to be used in AngularJS
.directive('ng2', downgradeComponent({component: Ng2Component})); .directive(
'ng2',
downgradeComponent({component: Ng2Component, inputs: ['nameProp: name']}));
// This is the (AngularJS) application bootstrap element // This is the (AngularJS) application bootstrap element
// Notice that it is actually a downgraded Angular component // Notice that it is actually a downgraded Angular component

View File

@ -2627,10 +2627,12 @@ export function main() {
} }
// Define `ng1Module` // Define `ng1Module`
const ng1Module = angular.module('ng1Module', []) const ng1Module =
angular.module('ng1Module', [])
.directive('ng1A', () => ng1DirectiveA) .directive('ng1A', () => ng1DirectiveA)
.directive('ng1B', () => ng1DirectiveB) .directive('ng1B', () => ng1DirectiveB)
.directive('ng2', downgradeComponent({component: Ng2Component})); .directive(
'ng2', downgradeComponent({component: Ng2Component, inputs: ['show']}));
// Define `Ng2Module` // Define `Ng2Module`
@NgModule({ @NgModule({
@ -2727,10 +2729,12 @@ export function main() {
} }
// Define `ng1Module` // Define `ng1Module`
const ng1Module = angular.module('ng1Module', []) const ng1Module =
angular.module('ng1Module', [])
.directive('ng1A', () => ng1DirectiveA) .directive('ng1A', () => ng1DirectiveA)
.directive('ng1B', () => ng1DirectiveB) .directive('ng1B', () => ng1DirectiveB)
.directive('ng2', downgradeComponent({component: Ng2Component})); .directive(
'ng2', downgradeComponent({component: Ng2Component, inputs: ['show']}));
// Define `Ng2Module` // Define `Ng2Module`
@NgModule({ @NgModule({
@ -3082,7 +3086,11 @@ export function main() {
const ng1Module = angular.module('ng1', []) const ng1Module = angular.module('ng1', [])
.component('ng1X', ng1Component) .component('ng1X', ng1Component)
.directive('ng2A', downgradeComponent({component: Ng2ComponentA})) .directive('ng2A', downgradeComponent({component: Ng2ComponentA}))
.directive('ng2B', downgradeComponent({component: Ng2ComponentB})); .directive('ng2B', downgradeComponent({
component: Ng2ComponentB,
inputs: ['ng2BInputA: ng2BInput1', 'ng2BInputC'],
outputs: ['ng2BOutputC']
}));
// Define `Ng2Module` // Define `Ng2Module`
@NgModule({ @NgModule({

View File

@ -193,7 +193,7 @@ export declare class Compiler {
compileModuleAndAllComponentsSync<T>(moduleType: Type<T>): ModuleWithComponentFactories<T>; compileModuleAndAllComponentsSync<T>(moduleType: Type<T>): ModuleWithComponentFactories<T>;
compileModuleAsync<T>(moduleType: Type<T>): Promise<NgModuleFactory<T>>; compileModuleAsync<T>(moduleType: Type<T>): Promise<NgModuleFactory<T>>;
compileModuleSync<T>(moduleType: Type<T>): NgModuleFactory<T>; compileModuleSync<T>(moduleType: Type<T>): NgModuleFactory<T>;
/** @deprecated */ getNgContentSelectors(component: Type<any>): string[]; getNgContentSelectors(component: Type<any>): string[];
} }
/** @experimental */ /** @experimental */
@ -226,15 +226,6 @@ export interface ComponentDecorator {
/** @stable */ /** @stable */
export declare abstract class ComponentFactory<C> { export declare abstract class ComponentFactory<C> {
readonly abstract componentType: Type<any>; readonly abstract componentType: Type<any>;
readonly abstract inputs: {
propName: string;
templateName: string;
}[];
readonly abstract ngContentSelectors: string[];
readonly abstract outputs: {
propName: string;
templateName: string;
}[];
readonly abstract selector: string; readonly abstract selector: string;
abstract create(injector: Injector, projectableNodes?: any[][], rootSelectorOrNode?: string | any, ngModule?: NgModuleRef<any>): ComponentRef<C>; abstract create(injector: Injector, projectableNodes?: any[][], rootSelectorOrNode?: string | any, ngModule?: NgModuleRef<any>): ComponentRef<C>;
} }

View File

@ -1,9 +1,9 @@
/** @experimental */ /** @experimental */
export declare function downgradeComponent(info: { export declare function downgradeComponent(info: {
component: Type<any>; component: Type<any>;
/** @deprecated */ inputs?: string[]; inputs?: string[];
/** @deprecated */ outputs?: string[]; outputs?: string[];
/** @deprecated */ selectors?: string[]; selectors?: string[];
}): any; }): any;
/** @experimental */ /** @experimental */