Skip to content
Permalink
Browse files

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

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.
  • Loading branch information...
tbosch authored and chuckjaz committed Mar 14, 2017
1 parent 1171f91 commit a3e32fb7e14b5b131c90987559e446769d25d6b7
@@ -156,7 +156,6 @@ 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,7 +279,6 @@ 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,12 +145,7 @@ 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(
// The inputs and outputs here must match the relevant names of the properties on the
// "downgraded" component
{component: Ng2HeroesComponent, inputs: ['heroes'], outputs: ['addHero', 'removeHero']}));
ng1AppModule.directive('ng2Heroes', downgradeComponent({component: Ng2HeroesComponent}));
// #enddocregion

// #docregion example-app
@@ -6,37 +6,23 @@
* 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
* `"prop: attr"`; or simply `"propAndAttr" where the property
* and attribute have the same identifier.
*/
export class PropertyBinding {
prop: string;
attr: string;
bracketAttr: string;
bracketParenAttr: string;
parenAttr: string;
onAttr: string;
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})]`;

This file was deleted.

@@ -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());
let ngContentSelectors = this.componentFactory.ngContentSelectors;
return 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;
/**
* 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;

for (let i = 0, ii = ngContentSelectors.length; i < ii; ++i) {
projectableNodes[i] = [];
}
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);
}
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 _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);
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;
}

This file was deleted.

0 comments on commit a3e32fb

Please sign in to comment.
You can’t perform that action at this time.