Skip to content

Commit

Permalink
feat(upgrade): use `ComponentFactory.inputs/outputs/ngContentSelector…
Browse files Browse the repository at this point in the history
…s` (#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.
  • Loading branch information
tbosch authored and mhevery committed Mar 17, 2017
1 parent 791534f commit 9429032
Show file tree
Hide file tree
Showing 22 changed files with 92 additions and 416 deletions.
1 change: 0 additions & 1 deletion packages/compiler/src/metadata_resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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});
Expand Down
1 change: 0 additions & 1 deletion packages/compiler/test/aot/compiler_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,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((generatedFiles) => {
const genFile = generatedFiles.find(genFile => genFile.srcFileUrl === '/app/app.ts');
const createComponentFactoryCall =
Expand Down
7 changes: 1 addition & 6 deletions packages/examples/upgrade/static/ts/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 1 addition & 15 deletions packages/upgrade/src/common/component_info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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})]`;
Expand Down
20 changes: 0 additions & 20 deletions packages/upgrade/src/common/content_projection_helper.ts

This file was deleted.

33 changes: 6 additions & 27 deletions packages/upgrade/src/common/downgrade_component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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();
Expand Down
84 changes: 38 additions & 46 deletions packages/upgrade/src/common/downgrade_component_adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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,
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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 = {};
Expand All @@ -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;

Expand Down Expand Up @@ -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)}'!`);
}
}
}
Expand All @@ -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 {
Expand All @@ -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;
}
24 changes: 0 additions & 24 deletions packages/upgrade/src/common/ng_content_selector_helper.ts

This file was deleted.

Loading

0 comments on commit 9429032

Please sign in to comment.