Revert "feat(upgrade): use `ComponentFactory.inputs/outputs/ngContentSelectors`"
This reverts commit a3e32fb7e1
.
This commit is contained in:
parent
a3e32fb7e1
commit
c439742a54
|
@ -156,6 +156,7 @@ export class CompileMetadataResolver {
|
|||
const templateName = inputs[propName];
|
||||
factory.inputs.push({propName, templateName});
|
||||
}
|
||||
const outputsArr: {propName: string, templateName: string}[] = [];
|
||||
for (let propName in outputs) {
|
||||
const templateName = outputs[propName];
|
||||
factory.outputs.push({propName, templateName});
|
||||
|
|
|
@ -279,6 +279,7 @@ describe('compiler (unbundled Angular)', () => {
|
|||
const host = new MockCompilerHost(['/app/app.ts'], FILES, angularFiles);
|
||||
const aotHost = new MockAotCompilerHost(host);
|
||||
let generatedFiles: GeneratedFile[];
|
||||
const warnSpy = spyOn(console, 'warn');
|
||||
compile(host, aotHost, expectNoDiagnostics).then((f) => generatedFiles = f);
|
||||
|
||||
tick();
|
||||
|
|
|
@ -145,7 +145,12 @@ ng1AppModule.factory('heroesService', downgradeInjectable(HeroesService));
|
|||
|
||||
// #docregion ng2-heroes-wrapper
|
||||
// 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
|
||||
|
||||
// #docregion example-app
|
||||
|
|
|
@ -6,6 +6,15 @@
|
|||
* 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
|
||||
* and an attribute name. It is parsed from a string of the form
|
||||
|
@ -13,6 +22,8 @@
|
|||
* and attribute have the same identifier.
|
||||
*/
|
||||
export class PropertyBinding {
|
||||
prop: string;
|
||||
attr: string;
|
||||
bracketAttr: string;
|
||||
bracketParenAttr: string;
|
||||
parenAttr: string;
|
||||
|
@ -20,9 +31,12 @@ export class PropertyBinding {
|
|||
bindAttr: string;
|
||||
bindonAttr: string;
|
||||
|
||||
constructor(public prop: string, public attr: string) { this.parseBinding(); }
|
||||
constructor(public binding: string) { this.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.parenAttr = `(${this.attr})`;
|
||||
this.bracketParenAttr = `[(${this.attr})]`;
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ import {ComponentFactory, ComponentFactoryResolver, Injector, Type} from '@angul
|
|||
import * as angular from './angular1';
|
||||
import {$COMPILE, $INJECTOR, $PARSE, INJECTOR_KEY, REQUIRE_INJECTOR, REQUIRE_NG_MODEL} from './constants';
|
||||
import {DowngradeComponentAdapter} from './downgrade_component_adapter';
|
||||
import {NgContentSelectorHelper} from './ng_content_selector_helper';
|
||||
import {controllerKey, getComponentName} from './util';
|
||||
|
||||
let downgradeCount = 0;
|
||||
|
@ -37,6 +38,15 @@ let downgradeCount = 0;
|
|||
*
|
||||
* {@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
|
||||
*
|
||||
* 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:
|
||||
*
|
||||
* * `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
|
||||
*/
|
||||
export function downgradeComponent(info: {
|
||||
export function downgradeComponent(info: /* ComponentInfo */ {
|
||||
component: Type<any>;
|
||||
/** @deprecated since v4. This parameter is no longer used */
|
||||
inputs?: string[];
|
||||
/** @deprecated since v4. This parameter is no longer used */
|
||||
outputs?: string[];
|
||||
/** @deprecated since v4. This parameter is no longer used */
|
||||
selectors?: string[];
|
||||
selectors?: string[]
|
||||
}): any /* angular.IInjectable */ {
|
||||
const idPrefix = `NG2_UPGRADE_${downgradeCount++}_`;
|
||||
let idCount = 0;
|
||||
|
@ -93,7 +114,7 @@ export function downgradeComponent(info: {
|
|||
const id = idPrefix + (idCount++);
|
||||
const injectorPromise = new ParentInjectorPromise(element);
|
||||
const facade = new DowngradeComponentAdapter(
|
||||
id, element, attrs, scope, ngModel, injector, $injector, $compile, $parse,
|
||||
id, info, element, attrs, scope, ngModel, injector, $injector, $compile, $parse,
|
||||
componentFactory);
|
||||
|
||||
const projectableNodes = facade.compileContents();
|
||||
|
|
|
@ -9,8 +9,9 @@
|
|||
import {ChangeDetectorRef, ComponentFactory, ComponentRef, EventEmitter, Injector, OnChanges, ReflectiveInjector, SimpleChange, SimpleChanges, Type} from '@angular/core';
|
||||
|
||||
import * as angular from './angular1';
|
||||
import {PropertyBinding} from './component_info';
|
||||
import {ComponentInfo, PropertyBinding} from './component_info';
|
||||
import {$SCOPE} from './constants';
|
||||
import {NgContentSelectorHelper} from './ng_content_selector_helper';
|
||||
import {getAttributesAsArray, getComponentName, hookupNgModel} from './util';
|
||||
|
||||
const INITIAL_VALUE = {
|
||||
|
@ -26,7 +27,7 @@ export class DowngradeComponentAdapter {
|
|||
private changeDetector: ChangeDetectorRef = null;
|
||||
|
||||
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 ngModel: angular.INgModelController, private parentInjector: Injector,
|
||||
private $injector: angular.IInjectorService, private $compile: angular.ICompileService,
|
||||
|
@ -66,9 +67,9 @@ export class DowngradeComponentAdapter {
|
|||
|
||||
setupInputs(): void {
|
||||
const attrs = this.attrs;
|
||||
const inputs = this.componentFactory.inputs || [];
|
||||
const inputs = this.info.inputs || [];
|
||||
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;
|
||||
|
||||
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) {
|
||||
// Detect: OnChanges interface
|
||||
this.inputChanges = {};
|
||||
|
@ -117,9 +118,9 @@ export class DowngradeComponentAdapter {
|
|||
|
||||
setupOutputs() {
|
||||
const attrs = this.attrs;
|
||||
const outputs = this.componentFactory.outputs || [];
|
||||
const outputs = this.info.outputs || [];
|
||||
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 assignExpr = false;
|
||||
|
||||
|
@ -157,7 +158,7 @@ export class DowngradeComponentAdapter {
|
|||
});
|
||||
} else {
|
||||
throw new Error(
|
||||
`Missing emitter '${output.prop}' on component '${getComponentName(this.componentFactory.componentType)}'!`);
|
||||
`Missing emitter '${output.prop}' on component '${getComponentName(this.info.component)}'!`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -182,31 +183,49 @@ export class DowngradeComponentAdapter {
|
|||
}
|
||||
|
||||
groupProjectableNodes() {
|
||||
let ngContentSelectors = this.componentFactory.ngContentSelectors;
|
||||
return groupNodesBySelector(ngContentSelectors, this.element.contents());
|
||||
const ngContentSelectorHelper =
|
||||
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.
|
||||
*/
|
||||
private _groupNodesBySelector(ngContentSelectors: string[], nodes: Node[]): Node[][] {
|
||||
const projectableNodes: Node[][] = [];
|
||||
let wildcardNgContentIndex: number;
|
||||
|
||||
for (let i = 0, ii = ngContentSelectors.length; i < ii; ++i) {
|
||||
projectableNodes[i] = [];
|
||||
}
|
||||
|
||||
for (let j = 0, jj = nodes.length; j < jj; ++j) {
|
||||
const node = nodes[j];
|
||||
const ngContentIndex = findMatchingNgContentIndex(node, ngContentSelectors);
|
||||
if (ngContentIndex != null) {
|
||||
projectableNodes[ngContentIndex].push(node);
|
||||
}
|
||||
}
|
||||
|
||||
return projectableNodes;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Group a set of DOM nodes into `ngContent` groups, based on the given content selectors.
|
||||
*/
|
||||
export function groupNodesBySelector(ngContentSelectors: string[], nodes: Node[]): Node[][] {
|
||||
const projectableNodes: Node[][] = [];
|
||||
let wildcardNgContentIndex: number;
|
||||
let _matches: (this: any, selector: string) => boolean;
|
||||
|
||||
for (let i = 0, ii = ngContentSelectors.length; i < ii; ++i) {
|
||||
projectableNodes[i] = [];
|
||||
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;
|
||||
}
|
||||
|
||||
for (let j = 0, jj = nodes.length; j < jj; ++j) {
|
||||
const node = nodes[j];
|
||||
const ngContentIndex = findMatchingNgContentIndex(node, ngContentSelectors);
|
||||
if (ngContentIndex != null) {
|
||||
projectableNodes[ngContentIndex].push(node);
|
||||
}
|
||||
}
|
||||
|
||||
return projectableNodes;
|
||||
return _matches.call(el, selector);
|
||||
}
|
||||
|
||||
function findMatchingNgContentIndex(element: any, ngContentSelectors: string[]): number {
|
||||
|
@ -228,15 +247,4 @@ function findMatchingNgContentIndex(element: any, ngContentSelectors: string[]):
|
|||
ngContentIndices.push(wildcardNgContentIndex);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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 || ['*'];
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -6,15 +6,19 @@
|
|||
* 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 {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
|
||||
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 {downgradeComponent} from '../common/downgrade_component';
|
||||
import {downgradeInjectable} from '../common/downgrade_injectable';
|
||||
import {NgContentSelectorHelper} from '../common/ng_content_selector_helper';
|
||||
import {Deferred, controllerKey, onError} from '../common/util';
|
||||
|
||||
import {DynamicNgContentSelectorHelper} from './ng_content_selector_helper';
|
||||
import {UpgradeNg1ComponentAdapterBuilder} from './upgrade_ng1_adapter';
|
||||
|
||||
let upgradeCount: number = 0;
|
||||
|
@ -100,6 +104,7 @@ let upgradeCount: number = 0;
|
|||
*/
|
||||
export class UpgradeAdapter {
|
||||
private idPrefix: string = `NG2_UPGRADE_${upgradeCount++}_`;
|
||||
private directiveResolver: DirectiveResolver = new DirectiveResolver();
|
||||
private downgradedComponents: Type<any>[] = [];
|
||||
/**
|
||||
* 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 {
|
||||
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: [
|
||||
{provide: $INJECTOR, useFactory: () => ng1Injector},
|
||||
{provide: $COMPILE, useFactory: () => ng1Injector.get($COMPILE)},
|
||||
{provide: NgContentSelectorHelper, useClass: DynamicNgContentSelectorHelper},
|
||||
this.upgradedProviders
|
||||
],
|
||||
imports: [this.ng2AppModule],
|
||||
|
|
|
@ -10,6 +10,7 @@ import {Injector, NgModule, NgZone, Testability} from '@angular/core';
|
|||
|
||||
import * as angular from '../common/angular1';
|
||||
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 {angular1Providers, setTempInjectorRef} from './angular1_providers';
|
||||
|
@ -129,7 +130,7 @@ import {angular1Providers, setTempInjectorRef} from './angular1_providers';
|
|||
*
|
||||
* @experimental
|
||||
*/
|
||||
@NgModule({providers: [angular1Providers]})
|
||||
@NgModule({providers: [angular1Providers, NgContentSelectorHelper]})
|
||||
export class UpgradeModule {
|
||||
/**
|
||||
* The AngularJS `$injector` for the upgrade application.
|
||||
|
|
|
@ -11,7 +11,8 @@ import {PropertyBinding} from '@angular/upgrade/src/common/component_info';
|
|||
export function main() {
|
||||
describe('PropertyBinding', () => {
|
||||
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.attr).toEqual('someBinding');
|
||||
expect(binding.bracketAttr).toEqual('[someBinding]');
|
||||
|
@ -23,7 +24,21 @@ export function main() {
|
|||
});
|
||||
|
||||
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.attr).toEqual('someAttr');
|
||||
expect(binding.bracketAttr).toEqual('[someAttr]');
|
||||
|
|
|
@ -6,13 +6,25 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
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';
|
||||
|
||||
|
||||
export function main() {
|
||||
describe('DowngradeComponentAdapter', () => {
|
||||
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', () => {
|
||||
const contentNodes = nodes(
|
||||
'<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>');
|
||||
|
||||
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[1]).toEqual(nodes('<span>span content</span>'));
|
||||
|
@ -41,7 +54,8 @@ export function main() {
|
|||
'<div class="x"><span>div-2 content</span></div>');
|
||||
|
||||
const selectors = ['.x', '*', 'input[type=date]'];
|
||||
const projectableNodes = groupNodesBySelector(selectors, contentNodes);
|
||||
const adapter = createAdapter(selectors, contentNodes);
|
||||
const projectableNodes = adapter.groupProjectableNodes();
|
||||
|
||||
expect(projectableNodes[0])
|
||||
.toEqual(nodes(
|
||||
|
@ -56,7 +70,8 @@ export function main() {
|
|||
|
||||
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, []);
|
||||
const adapter = createAdapter(selectors, []);
|
||||
const projectableNodes = adapter.groupProjectableNodes();
|
||||
expect(projectableNodes).toEqual([[], [], []]);
|
||||
});
|
||||
|
||||
|
@ -68,10 +83,12 @@ export function main() {
|
|||
'<span>span content</span>' +
|
||||
'<div class="x"><span>div-2 content</span></div>');
|
||||
|
||||
const projectableNodes = groupNodesBySelector([], contentNodes);
|
||||
const adapter1 = createAdapter([], contentNodes);
|
||||
const projectableNodes = adapter1.groupProjectableNodes();
|
||||
expect(projectableNodes).toEqual([]);
|
||||
|
||||
const noMatchSelectorNodes = groupNodesBySelector(['.not-there'], contentNodes);
|
||||
const adapter2 = createAdapter(['.not-there'], contentNodes);
|
||||
const noMatchSelectorNodes = adapter2.groupProjectableNodes();
|
||||
expect(noMatchSelectorNodes).toEqual([[]]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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([[]]);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -73,7 +73,7 @@ export function main() {
|
|||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
|
||||
expect(document.body.textContent).toEqual('1A;2A;ng1a;2B;ng1b;2C;1C;');
|
||||
// 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']);
|
||||
});
|
||||
}));
|
||||
|
||||
|
|
|
@ -72,14 +72,16 @@ export function main() {
|
|||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
const ng1Module = angular.module('ng1', [])
|
||||
.directive('ng2', downgradeComponent({component: Ng2Component}))
|
||||
.run(($rootScope: angular.IRootScopeService) => {
|
||||
$rootScope['items'] = [
|
||||
{id: 'a', subitems: [1, 2, 3]}, {id: 'b', subitems: [4, 5, 6]},
|
||||
{id: 'c', subitems: [7, 8, 9]}
|
||||
];
|
||||
});
|
||||
const ng1Module =
|
||||
angular.module('ng1', [])
|
||||
.directive(
|
||||
'ng2', downgradeComponent({component: Ng2Component, inputs: ['itemId']}))
|
||||
.run(($rootScope: angular.IRootScopeService) => {
|
||||
$rootScope['items'] = [
|
||||
{id: 'a', subitems: [1, 2, 3]}, {id: 'b', subitems: [4, 5, 6]},
|
||||
{id: 'c', subitems: [7, 8, 9]}
|
||||
];
|
||||
});
|
||||
|
||||
const element = html(`
|
||||
<ng2 ng-repeat="item in items" [item-id]="item.id">
|
||||
|
@ -160,7 +162,7 @@ export function main() {
|
|||
}
|
||||
|
||||
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 correct slot is targeted even with structural directives in play.
|
||||
|
|
|
@ -108,9 +108,15 @@ export function main() {
|
|||
};
|
||||
}
|
||||
|
||||
ng1Module.directive('ng2', downgradeComponent({
|
||||
component: Ng2Component,
|
||||
}));
|
||||
ng1Module.directive(
|
||||
'ng2', downgradeComponent({
|
||||
component: Ng2Component,
|
||||
inputs: ['literal', 'interpolate', 'oneWayA', 'oneWayB', 'twoWayA', 'twoWayB'],
|
||||
outputs: [
|
||||
'eventA', 'eventB', 'twoWayAEmitter: twoWayAChange',
|
||||
'twoWayBEmitter: twoWayBChange'
|
||||
]
|
||||
}));
|
||||
|
||||
@NgModule({
|
||||
declarations: [Ng2Component],
|
||||
|
|
|
@ -71,7 +71,9 @@ export function main() {
|
|||
};
|
||||
})
|
||||
// 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
|
||||
// Notice that it is actually a downgraded Angular component
|
||||
|
|
|
@ -2627,10 +2627,12 @@ export function main() {
|
|||
}
|
||||
|
||||
// Define `ng1Module`
|
||||
const ng1Module = angular.module('ng1Module', [])
|
||||
.directive('ng1A', () => ng1DirectiveA)
|
||||
.directive('ng1B', () => ng1DirectiveB)
|
||||
.directive('ng2', downgradeComponent({component: Ng2Component}));
|
||||
const ng1Module =
|
||||
angular.module('ng1Module', [])
|
||||
.directive('ng1A', () => ng1DirectiveA)
|
||||
.directive('ng1B', () => ng1DirectiveB)
|
||||
.directive(
|
||||
'ng2', downgradeComponent({component: Ng2Component, inputs: ['show']}));
|
||||
|
||||
// Define `Ng2Module`
|
||||
@NgModule({
|
||||
|
@ -2727,10 +2729,12 @@ export function main() {
|
|||
}
|
||||
|
||||
// Define `ng1Module`
|
||||
const ng1Module = angular.module('ng1Module', [])
|
||||
.directive('ng1A', () => ng1DirectiveA)
|
||||
.directive('ng1B', () => ng1DirectiveB)
|
||||
.directive('ng2', downgradeComponent({component: Ng2Component}));
|
||||
const ng1Module =
|
||||
angular.module('ng1Module', [])
|
||||
.directive('ng1A', () => ng1DirectiveA)
|
||||
.directive('ng1B', () => ng1DirectiveB)
|
||||
.directive(
|
||||
'ng2', downgradeComponent({component: Ng2Component, inputs: ['show']}));
|
||||
|
||||
// Define `Ng2Module`
|
||||
@NgModule({
|
||||
|
@ -3082,7 +3086,11 @@ export function main() {
|
|||
const ng1Module = angular.module('ng1', [])
|
||||
.component('ng1X', ng1Component)
|
||||
.directive('ng2A', downgradeComponent({component: Ng2ComponentA}))
|
||||
.directive('ng2B', downgradeComponent({component: Ng2ComponentB}));
|
||||
.directive('ng2B', downgradeComponent({
|
||||
component: Ng2ComponentB,
|
||||
inputs: ['ng2BInputA: ng2BInput1', 'ng2BInputC'],
|
||||
outputs: ['ng2BOutputC']
|
||||
}));
|
||||
|
||||
// Define `Ng2Module`
|
||||
@NgModule({
|
||||
|
|
|
@ -193,7 +193,7 @@ export declare class Compiler {
|
|||
compileModuleAndAllComponentsSync<T>(moduleType: Type<T>): ModuleWithComponentFactories<T>;
|
||||
compileModuleAsync<T>(moduleType: Type<T>): Promise<NgModuleFactory<T>>;
|
||||
compileModuleSync<T>(moduleType: Type<T>): NgModuleFactory<T>;
|
||||
/** @deprecated */ getNgContentSelectors(component: Type<any>): string[];
|
||||
getNgContentSelectors(component: Type<any>): string[];
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
|
@ -226,15 +226,6 @@ export interface ComponentDecorator {
|
|||
/** @stable */
|
||||
export declare abstract class ComponentFactory<C> {
|
||||
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;
|
||||
abstract create(injector: Injector, projectableNodes?: any[][], rootSelectorOrNode?: string | any, ngModule?: NgModuleRef<any>): ComponentRef<C>;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
/** @experimental */
|
||||
export declare function downgradeComponent(info: {
|
||||
component: Type<any>;
|
||||
/** @deprecated */ inputs?: string[];
|
||||
/** @deprecated */ outputs?: string[];
|
||||
/** @deprecated */ selectors?: string[];
|
||||
inputs?: string[];
|
||||
outputs?: string[];
|
||||
selectors?: string[];
|
||||
}): any;
|
||||
|
||||
/** @experimental */
|
||||
|
|
Loading…
Reference in New Issue