feat(upgrade): use ComponentFactory.inputs/outputs/ngContentSelectors
(#15214)
DEPRECATION: - the arguments `inputs` / `outputs` / `ngContentSelectors` of `downgradeComponent` are no longer used as Angular calculates these automatically now. - Compiler.getNgContentSelectors is deprecated. Use ComponentFactory.ngContentSelectors instead.
This commit is contained in:

committed by
Miško Hevery

parent
791534f2f4
commit
9429032da1
@ -6,15 +6,6 @@
|
||||
* 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
|
||||
@ -22,8 +13,6 @@ export interface ComponentInfo {
|
||||
* and attribute have the same identifier.
|
||||
*/
|
||||
export class PropertyBinding {
|
||||
prop: string;
|
||||
attr: string;
|
||||
bracketAttr: string;
|
||||
bracketParenAttr: string;
|
||||
parenAttr: string;
|
||||
@ -31,12 +20,9 @@ export class PropertyBinding {
|
||||
bindAttr: string;
|
||||
bindonAttr: string;
|
||||
|
||||
constructor(public binding: string) { this.parseBinding(); }
|
||||
constructor(public prop: string, public attr: 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})]`;
|
||||
|
@ -1,20 +0,0 @@
|
||||
/**
|
||||
* @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,7 +11,6 @@ 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;
|
||||
@ -38,15 +37,6 @@ 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
|
||||
@ -55,28 +45,17 @@ 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: /* ComponentInfo */ {
|
||||
export function downgradeComponent(info: {
|
||||
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[];
|
||||
selectors?: string[]
|
||||
/** @deprecated since v4. This parameter is no longer used */
|
||||
selectors?: string[];
|
||||
}): any /* angular.IInjectable */ {
|
||||
const idPrefix = `NG2_UPGRADE_${downgradeCount++}_`;
|
||||
let idCount = 0;
|
||||
@ -114,7 +93,7 @@ export function downgradeComponent(info: /* ComponentInfo */ {
|
||||
const id = idPrefix + (idCount++);
|
||||
const injectorPromise = new ParentInjectorPromise(element);
|
||||
const facade = new DowngradeComponentAdapter(
|
||||
id, info, element, attrs, scope, ngModel, injector, $injector, $compile, $parse,
|
||||
id, element, attrs, scope, ngModel, injector, $injector, $compile, $parse,
|
||||
componentFactory);
|
||||
|
||||
const projectableNodes = facade.compileContents();
|
||||
|
@ -9,9 +9,8 @@
|
||||
import {ChangeDetectorRef, ComponentFactory, ComponentRef, EventEmitter, Injector, OnChanges, ReflectiveInjector, SimpleChange, SimpleChanges, Type} from '@angular/core';
|
||||
|
||||
import * as angular from './angular1';
|
||||
import {ComponentInfo, PropertyBinding} from './component_info';
|
||||
import {PropertyBinding} from './component_info';
|
||||
import {$SCOPE} from './constants';
|
||||
import {NgContentSelectorHelper} from './ng_content_selector_helper';
|
||||
import {getAttributesAsArray, getComponentName, hookupNgModel} from './util';
|
||||
|
||||
const INITIAL_VALUE = {
|
||||
@ -27,7 +26,7 @@ export class DowngradeComponentAdapter {
|
||||
private changeDetector: ChangeDetectorRef = null;
|
||||
|
||||
constructor(
|
||||
private id: string, private info: ComponentInfo, private element: angular.IAugmentedJQuery,
|
||||
private id: string, 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,
|
||||
@ -67,9 +66,9 @@ export class DowngradeComponentAdapter {
|
||||
|
||||
setupInputs(): void {
|
||||
const attrs = this.attrs;
|
||||
const inputs = this.info.inputs || [];
|
||||
const inputs = this.componentFactory.inputs || [];
|
||||
for (let i = 0; i < inputs.length; i++) {
|
||||
const input = new PropertyBinding(inputs[i]);
|
||||
const input = new PropertyBinding(inputs[i].propName, inputs[i].templateName);
|
||||
let expr: any /** TODO #9100 */ = null;
|
||||
|
||||
if (attrs.hasOwnProperty(input.attr)) {
|
||||
@ -103,7 +102,7 @@ export class DowngradeComponentAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
const prototype = this.info.component.prototype;
|
||||
const prototype = this.componentFactory.componentType.prototype;
|
||||
if (prototype && (<OnChanges>prototype).ngOnChanges) {
|
||||
// Detect: OnChanges interface
|
||||
this.inputChanges = {};
|
||||
@ -118,9 +117,9 @@ export class DowngradeComponentAdapter {
|
||||
|
||||
setupOutputs() {
|
||||
const attrs = this.attrs;
|
||||
const outputs = this.info.outputs || [];
|
||||
const outputs = this.componentFactory.outputs || [];
|
||||
for (let j = 0; j < outputs.length; j++) {
|
||||
const output = new PropertyBinding(outputs[j]);
|
||||
const output = new PropertyBinding(outputs[j].propName, outputs[j].templateName);
|
||||
let expr: any /** TODO #9100 */ = null;
|
||||
let assignExpr = false;
|
||||
|
||||
@ -158,7 +157,7 @@ export class DowngradeComponentAdapter {
|
||||
});
|
||||
} else {
|
||||
throw new Error(
|
||||
`Missing emitter '${output.prop}' on component '${getComponentName(this.info.component)}'!`);
|
||||
`Missing emitter '${output.prop}' on component '${getComponentName(this.componentFactory.componentType)}'!`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -183,49 +182,31 @@ export class DowngradeComponentAdapter {
|
||||
}
|
||||
|
||||
groupProjectableNodes() {
|
||||
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;
|
||||
let ngContentSelectors = this.componentFactory.ngContentSelectors;
|
||||
return groupNodesBySelector(ngContentSelectors, this.element.contents());
|
||||
}
|
||||
}
|
||||
|
||||
let _matches: (this: any, selector: string) => boolean;
|
||||
/**
|
||||
* 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;
|
||||
|
||||
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 i = 0, ii = ngContentSelectors.length; i < ii; ++i) {
|
||||
projectableNodes[i] = [];
|
||||
}
|
||||
return _matches.call(el, selector);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
function findMatchingNgContentIndex(element: any, ngContentSelectors: string[]): number {
|
||||
@ -247,4 +228,15 @@ 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;
|
||||
}
|
||||
|
@ -1,24 +0,0 @@
|
||||
/**
|
||||
* @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 || ['*'];
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
/**
|
||||
* @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,19 +6,15 @@
|
||||
* 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;
|
||||
@ -104,7 +100,6 @@ 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.
|
||||
@ -190,10 +185,7 @@ export class UpgradeAdapter {
|
||||
downgradeNg2Component(component: Type<any>): Function {
|
||||
this.downgradedComponents.push(component);
|
||||
|
||||
const metadata: Directive = this.directiveResolver.resolve(component);
|
||||
const info: ComponentInfo = {component, inputs: metadata.inputs, outputs: metadata.outputs};
|
||||
|
||||
return downgradeComponent(info);
|
||||
return downgradeComponent({component});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -561,7 +553,6 @@ export class UpgradeAdapter {
|
||||
providers: [
|
||||
{provide: $INJECTOR, useFactory: () => ng1Injector},
|
||||
{provide: $COMPILE, useFactory: () => ng1Injector.get($COMPILE)},
|
||||
{provide: NgContentSelectorHelper, useClass: DynamicNgContentSelectorHelper},
|
||||
this.upgradedProviders
|
||||
],
|
||||
imports: [this.ng2AppModule],
|
||||
|
@ -10,7 +10,6 @@ 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';
|
||||
@ -130,7 +129,7 @@ import {angular1Providers, setTempInjectorRef} from './angular1_providers';
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@NgModule({providers: [angular1Providers, NgContentSelectorHelper]})
|
||||
@NgModule({providers: [angular1Providers]})
|
||||
export class UpgradeModule {
|
||||
/**
|
||||
* The AngularJS `$injector` for the upgrade application.
|
||||
|
Reference in New Issue
Block a user