refactor(upgrade): use shared code in downgradeNg2Component()
(#14037)
This unified the implementations of dynamic's `downgradeNg2Component()` and static's `downgradeComponent()`.
This commit is contained in:

committed by
Miško Hevery

parent
1367cd9569
commit
ea63676970
@ -309,23 +309,12 @@ export class PlatformRef_ extends PlatformRef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _bootstrapModuleWithZone<M>(
|
private _bootstrapModuleWithZone<M>(
|
||||||
moduleType: Type<M>, compilerOptions: CompilerOptions|CompilerOptions[] = [], ngZone: NgZone,
|
moduleType: Type<M>, compilerOptions: CompilerOptions|CompilerOptions[] = [],
|
||||||
componentFactoryCallback?: any): Promise<NgModuleRef<M>> {
|
ngZone: NgZone): Promise<NgModuleRef<M>> {
|
||||||
const compilerFactory: CompilerFactory = this.injector.get(CompilerFactory);
|
const compilerFactory: CompilerFactory = this.injector.get(CompilerFactory);
|
||||||
const compiler = compilerFactory.createCompiler(
|
const compiler = compilerFactory.createCompiler(
|
||||||
Array.isArray(compilerOptions) ? compilerOptions : [compilerOptions]);
|
Array.isArray(compilerOptions) ? compilerOptions : [compilerOptions]);
|
||||||
|
|
||||||
// ugly internal api hack: generate host component factories for all declared components and
|
|
||||||
// pass the factories into the callback - this is used by UpdateAdapter to get hold of all
|
|
||||||
// factories.
|
|
||||||
if (componentFactoryCallback) {
|
|
||||||
return compiler.compileModuleAndAllComponentsAsync(moduleType)
|
|
||||||
.then(({ngModuleFactory, componentFactories}) => {
|
|
||||||
componentFactoryCallback(componentFactories);
|
|
||||||
return this._bootstrapModuleFactoryWithZone(ngModuleFactory, ngZone);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return compiler.compileModuleAsync(moduleType)
|
return compiler.compileModuleAsync(moduleType)
|
||||||
.then((moduleFactory) => this._bootstrapModuleFactoryWithZone(moduleFactory, ngZone));
|
.then((moduleFactory) => this._bootstrapModuleFactoryWithZone(moduleFactory, ngZone));
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ export const $TEMPLATE_REQUEST = '$templateRequest';
|
|||||||
export const $$TESTABILITY = '$$testability';
|
export const $$TESTABILITY = '$$testability';
|
||||||
|
|
||||||
export const COMPILER_KEY = '$$angularCompiler';
|
export const COMPILER_KEY = '$$angularCompiler';
|
||||||
export const COMPONENT_FACTORY_REF_MAP_KEY = '$$angularComponentFactoryRefMap';
|
export const GROUP_PROJECTABLE_NODES_KEY = '$$angularGroupProjectableNodes';
|
||||||
export const INJECTOR_KEY = '$$angularInjector';
|
export const INJECTOR_KEY = '$$angularInjector';
|
||||||
export const NG_ZONE_KEY = '$$angularNgZone';
|
export const NG_ZONE_KEY = '$$angularNgZone';
|
||||||
|
|
||||||
|
@ -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,7 +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 {controllerKey} from './util';
|
import {controllerKey, getComponentName} from './util';
|
||||||
|
|
||||||
let downgradeCount = 0;
|
let downgradeCount = 0;
|
||||||
|
|
||||||
@ -86,7 +86,8 @@ export function downgradeComponent(info: /* ComponentInfo */ {
|
|||||||
// triggered by `UpgradeNg1ComponentAdapterBuilder`, before the Angular templates have
|
// triggered by `UpgradeNg1ComponentAdapterBuilder`, before the Angular templates have
|
||||||
// been compiled.
|
// been compiled.
|
||||||
|
|
||||||
const parentInjector: Injector | ParentInjectorPromise = required[0] || $injector.get(INJECTOR_KEY);
|
const parentInjector: Injector|ParentInjectorPromise =
|
||||||
|
required[0] || $injector.get(INJECTOR_KEY);
|
||||||
const ngModel: angular.INgModelController = required[1];
|
const ngModel: angular.INgModelController = required[1];
|
||||||
|
|
||||||
const downgradeFn = (injector: Injector) => {
|
const downgradeFn = (injector: Injector) => {
|
||||||
@ -96,13 +97,14 @@ export function downgradeComponent(info: /* ComponentInfo */ {
|
|||||||
componentFactoryResolver.resolveComponentFactory(info.component);
|
componentFactoryResolver.resolveComponentFactory(info.component);
|
||||||
|
|
||||||
if (!componentFactory) {
|
if (!componentFactory) {
|
||||||
throw new Error('Expecting ComponentFactory for: ' + info.component);
|
throw new Error('Expecting ComponentFactory for: ' + getComponentName(info.component));
|
||||||
}
|
}
|
||||||
|
|
||||||
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, info, element, attrs, scope, ngModel, injector, $compile, $parse, componentFactory);
|
id, info, element, attrs, scope, ngModel, injector, $injector, $compile, $parse,
|
||||||
|
componentFactory);
|
||||||
|
|
||||||
const projectableNodes = facade.compileContents();
|
const projectableNodes = facade.compileContents();
|
||||||
facade.createComponent(projectableNodes);
|
facade.createComponent(projectableNodes);
|
||||||
|
@ -11,7 +11,8 @@ import {ChangeDetectorRef, ComponentFactory, ComponentRef, EventEmitter, Injecto
|
|||||||
import * as angular from './angular1';
|
import * as angular from './angular1';
|
||||||
import {ComponentInfo, PropertyBinding} from './component_info';
|
import {ComponentInfo, PropertyBinding} from './component_info';
|
||||||
import {$SCOPE} from './constants';
|
import {$SCOPE} from './constants';
|
||||||
import {hookupNgModel} from './util';
|
import {ContentProjectionHelper} from './content_projection_helper';
|
||||||
|
import {getComponentName, hookupNgModel} from './util';
|
||||||
|
|
||||||
const INITIAL_VALUE = {
|
const INITIAL_VALUE = {
|
||||||
__UNINITIALIZED__: true
|
__UNINITIALIZED__: true
|
||||||
@ -29,24 +30,32 @@ export class DowngradeComponentAdapter {
|
|||||||
private id: string, private info: ComponentInfo, 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 $compile: angular.ICompileService, private $parse: angular.IParseService,
|
private $injector: angular.IInjectorService, private $compile: angular.ICompileService,
|
||||||
private componentFactory: ComponentFactory<any>) {
|
private $parse: angular.IParseService, private componentFactory: ComponentFactory<any>) {
|
||||||
(this.element[0] as any).id = id;
|
(this.element[0] as any).id = id;
|
||||||
this.componentScope = scope.$new();
|
this.componentScope = scope.$new();
|
||||||
}
|
}
|
||||||
|
|
||||||
compileContents(): Node[][] {
|
compileContents(): Node[][] {
|
||||||
const projectableNodes: Node[][] = [];
|
const compiledProjectableNodes: Node[][] = [];
|
||||||
const linkFn = this.$compile(this.element.contents());
|
|
||||||
|
// The projected content has to be grouped, before it is compiled.
|
||||||
|
const projectionHelper: ContentProjectionHelper =
|
||||||
|
this.parentInjector.get(ContentProjectionHelper);
|
||||||
|
const projectableNodes: Node[][] = projectionHelper.groupProjectableNodes(
|
||||||
|
this.$injector, this.info.component, this.element.contents());
|
||||||
|
const linkFns = projectableNodes.map(nodes => this.$compile(nodes));
|
||||||
|
|
||||||
this.element.empty();
|
this.element.empty();
|
||||||
|
|
||||||
linkFn(this.scope, (clone: Node[]) => {
|
linkFns.forEach(linkFn => {
|
||||||
projectableNodes.push(clone);
|
linkFn(this.scope, (clone: Node[]) => {
|
||||||
this.element.append(clone);
|
compiledProjectableNodes.push(clone);
|
||||||
|
this.element.append(clone);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return projectableNodes;
|
return compiledProjectableNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
createComponent(projectableNodes: Node[][]) {
|
createComponent(projectableNodes: Node[][]) {
|
||||||
@ -162,7 +171,7 @@ export class DowngradeComponentAdapter {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Missing emitter '${output.prop}' on component '${this.info.component}'!`);
|
`Missing emitter '${output.prop}' on component '${getComponentName(this.info.component)}'!`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,8 @@
|
|||||||
* 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_js';
|
import {Type} from '@angular/core';
|
||||||
|
import * as angular from './angular1';
|
||||||
|
|
||||||
export function onError(e: any) {
|
export function onError(e: any) {
|
||||||
// TODO: (misko): We seem to not have a stack trace here!
|
// TODO: (misko): We seem to not have a stack trace here!
|
||||||
@ -36,6 +37,11 @@ export function getAttributesAsArray(node: Node): [string, string][] {
|
|||||||
return asArray || [];
|
return asArray || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getComponentName(component: Type<any>): string {
|
||||||
|
// Return the name of the component or the first line of its stringified version.
|
||||||
|
return (component as any).overriddenName || component.name || component.toString().split('\n')[0];
|
||||||
|
}
|
||||||
|
|
||||||
export class Deferred<R> {
|
export class Deferred<R> {
|
||||||
promise: Promise<R>;
|
promise: Promise<R>;
|
||||||
resolve: (value?: R|PromiseLike<R>) => void;
|
resolve: (value?: R|PromiseLike<R>) => void;
|
||||||
@ -50,8 +56,9 @@ export class Deferred<R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if the passed-in component implements the subset of
|
* @return Whether the passed-in component implements the subset of the
|
||||||
* ControlValueAccessor needed for AngularJS ng-model compatibility.
|
* `ControlValueAccessor` interface needed for AngularJS `ng-model`
|
||||||
|
* compatibility.
|
||||||
*/
|
*/
|
||||||
function supportsNgModel(component: any) {
|
function supportsNgModel(component: any) {
|
||||||
return typeof component.writeValue === 'function' &&
|
return typeof component.writeValue === 'function' &&
|
||||||
@ -59,8 +66,8 @@ function supportsNgModel(component: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Glue the AngularJS ngModelController if it exists to the component if it
|
* Glue the AngularJS `NgModelController` (if it exists) to the component
|
||||||
* implements the needed subset of ControlValueAccessor.
|
* (if it implements the needed subset of the `ControlValueAccessor` interface).
|
||||||
*/
|
*/
|
||||||
export function hookupNgModel(ngModel: angular.INgModelController, component: any) {
|
export function hookupNgModel(ngModel: angular.INgModelController, component: any) {
|
||||||
if (ngModel && supportsNgModel(component)) {
|
if (ngModel && supportsNgModel(component)) {
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -1,159 +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 {ChangeDetectorRef, ComponentFactory, ComponentRef, EventEmitter, Injector, OnChanges, ReflectiveInjector, SimpleChange, SimpleChanges} from '@angular/core';
|
|
||||||
|
|
||||||
import * as angular from '../common/angular1';
|
|
||||||
import {$SCOPE} from '../common/constants';
|
|
||||||
|
|
||||||
import {ComponentInfo} from './metadata';
|
|
||||||
import {hookupNgModel} from './util';
|
|
||||||
|
|
||||||
const INITIAL_VALUE = {
|
|
||||||
__UNINITIALIZED__: true
|
|
||||||
};
|
|
||||||
|
|
||||||
export class DowngradeNg2ComponentAdapter {
|
|
||||||
component: any = null;
|
|
||||||
inputChangeCount: number = 0;
|
|
||||||
inputChanges: SimpleChanges = null;
|
|
||||||
componentRef: ComponentRef<any> = null;
|
|
||||||
changeDetector: ChangeDetectorRef = null;
|
|
||||||
componentScope: angular.IScope;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private info: ComponentInfo, private element: angular.IAugmentedJQuery,
|
|
||||||
private attrs: angular.IAttributes, private scope: angular.IScope,
|
|
||||||
private ngModel: angular.INgModelController, private parentInjector: Injector,
|
|
||||||
private parse: angular.IParseService, private componentFactory: ComponentFactory<any>) {
|
|
||||||
this.componentScope = scope.$new();
|
|
||||||
}
|
|
||||||
|
|
||||||
bootstrapNg2(projectableNodes: Node[][]) {
|
|
||||||
const childInjector = ReflectiveInjector.resolveAndCreate(
|
|
||||||
[{provide: $SCOPE, useValue: this.componentScope}], this.parentInjector);
|
|
||||||
|
|
||||||
this.componentRef =
|
|
||||||
this.componentFactory.create(childInjector, projectableNodes, this.element[0]);
|
|
||||||
this.changeDetector = this.componentRef.changeDetectorRef;
|
|
||||||
this.component = this.componentRef.instance;
|
|
||||||
|
|
||||||
hookupNgModel(this.ngModel, this.component);
|
|
||||||
}
|
|
||||||
|
|
||||||
setupInputs(): void {
|
|
||||||
const attrs = this.attrs;
|
|
||||||
const inputs = this.info.inputs || [];
|
|
||||||
for (let i = 0; i < inputs.length; i++) {
|
|
||||||
const input = inputs[i];
|
|
||||||
let expr: any /** TODO #9100 */ = null;
|
|
||||||
if (attrs.hasOwnProperty(input.attr)) {
|
|
||||||
const observeFn = ((prop: any /** TODO #9100 */) => {
|
|
||||||
let prevValue = INITIAL_VALUE;
|
|
||||||
return (value: any /** TODO #9100 */) => {
|
|
||||||
if (this.inputChanges !== null) {
|
|
||||||
this.inputChangeCount++;
|
|
||||||
this.inputChanges[prop] = new SimpleChange(
|
|
||||||
value, prevValue === INITIAL_VALUE ? value : prevValue,
|
|
||||||
prevValue === INITIAL_VALUE);
|
|
||||||
prevValue = value;
|
|
||||||
}
|
|
||||||
this.component[prop] = value;
|
|
||||||
};
|
|
||||||
})(input.prop);
|
|
||||||
attrs.$observe(input.attr, observeFn);
|
|
||||||
} else if (attrs.hasOwnProperty(input.bindAttr)) {
|
|
||||||
expr = (attrs as any /** TODO #9100 */)[input.bindAttr];
|
|
||||||
} else if (attrs.hasOwnProperty(input.bracketAttr)) {
|
|
||||||
expr = (attrs as any /** TODO #9100 */)[input.bracketAttr];
|
|
||||||
} else if (attrs.hasOwnProperty(input.bindonAttr)) {
|
|
||||||
expr = (attrs as any /** TODO #9100 */)[input.bindonAttr];
|
|
||||||
} else if (attrs.hasOwnProperty(input.bracketParenAttr)) {
|
|
||||||
expr = (attrs as any /** TODO #9100 */)[input.bracketParenAttr];
|
|
||||||
}
|
|
||||||
if (expr != null) {
|
|
||||||
const watchFn =
|
|
||||||
((prop: any /** TODO #9100 */) => (
|
|
||||||
value: any /** TODO #9100 */, prevValue: any /** TODO #9100 */) => {
|
|
||||||
if (this.inputChanges != null) {
|
|
||||||
this.inputChangeCount++;
|
|
||||||
this.inputChanges[prop] = new SimpleChange(prevValue, value, prevValue === value);
|
|
||||||
}
|
|
||||||
this.component[prop] = value;
|
|
||||||
})(input.prop);
|
|
||||||
this.componentScope.$watch(expr, watchFn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const prototype = this.info.type.prototype;
|
|
||||||
if (prototype && (<OnChanges>prototype).ngOnChanges) {
|
|
||||||
// Detect: OnChanges interface
|
|
||||||
this.inputChanges = {};
|
|
||||||
this.componentScope.$watch(() => this.inputChangeCount, () => {
|
|
||||||
const inputChanges = this.inputChanges;
|
|
||||||
this.inputChanges = {};
|
|
||||||
(<OnChanges>this.component).ngOnChanges(inputChanges);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.componentScope.$watch(() => this.changeDetector && this.changeDetector.detectChanges());
|
|
||||||
}
|
|
||||||
|
|
||||||
setupOutputs() {
|
|
||||||
const attrs = this.attrs;
|
|
||||||
const outputs = this.info.outputs || [];
|
|
||||||
for (let j = 0; j < outputs.length; j++) {
|
|
||||||
const output = outputs[j];
|
|
||||||
let expr: any /** TODO #9100 */ = null;
|
|
||||||
let assignExpr = false;
|
|
||||||
|
|
||||||
const bindonAttr =
|
|
||||||
output.bindonAttr ? output.bindonAttr.substring(0, output.bindonAttr.length - 6) : null;
|
|
||||||
const bracketParenAttr = output.bracketParenAttr ?
|
|
||||||
`[(${output.bracketParenAttr.substring(2, output.bracketParenAttr.length - 8)})]` :
|
|
||||||
null;
|
|
||||||
|
|
||||||
if (attrs.hasOwnProperty(output.onAttr)) {
|
|
||||||
expr = (attrs as any /** TODO #9100 */)[output.onAttr];
|
|
||||||
} else if (attrs.hasOwnProperty(output.parenAttr)) {
|
|
||||||
expr = (attrs as any /** TODO #9100 */)[output.parenAttr];
|
|
||||||
} else if (attrs.hasOwnProperty(bindonAttr)) {
|
|
||||||
expr = (attrs as any /** TODO #9100 */)[bindonAttr];
|
|
||||||
assignExpr = true;
|
|
||||||
} else if (attrs.hasOwnProperty(bracketParenAttr)) {
|
|
||||||
expr = (attrs as any /** TODO #9100 */)[bracketParenAttr];
|
|
||||||
assignExpr = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expr != null && assignExpr != null) {
|
|
||||||
const getter = this.parse(expr);
|
|
||||||
const setter = getter.assign;
|
|
||||||
if (assignExpr && !setter) {
|
|
||||||
throw new Error(`Expression '${expr}' is not assignable!`);
|
|
||||||
}
|
|
||||||
const emitter = this.component[output.prop] as EventEmitter<any>;
|
|
||||||
if (emitter) {
|
|
||||||
emitter.subscribe({
|
|
||||||
next: assignExpr ?
|
|
||||||
((setter: any) => (v: any /** TODO #9100 */) => setter(this.scope, v))(setter) :
|
|
||||||
((getter: any) => (v: any /** TODO #9100 */) =>
|
|
||||||
getter(this.scope, {$event: v}))(getter)
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw new Error(`Missing emitter '${output.prop}' on component '${this.info.selector}'!`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
registerCleanup() {
|
|
||||||
this.element.bind('$destroy', () => {
|
|
||||||
this.componentScope.$destroy();
|
|
||||||
this.componentRef.destroy();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +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 {DirectiveResolver} from '@angular/compiler';
|
|
||||||
import {Directive, Type} from '@angular/core';
|
|
||||||
|
|
||||||
import {PropertyBinding} from '../common/component_info';
|
|
||||||
|
|
||||||
|
|
||||||
const COMPONENT_SELECTOR = /^[\w|-]*$/;
|
|
||||||
const SKEWER_CASE = /-(\w)/g;
|
|
||||||
const directiveResolver = new DirectiveResolver();
|
|
||||||
|
|
||||||
export interface ComponentInfo {
|
|
||||||
type: Type<any>;
|
|
||||||
selector: string;
|
|
||||||
inputs?: PropertyBinding[];
|
|
||||||
outputs?: PropertyBinding[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getComponentInfo(type: Type<any>): ComponentInfo {
|
|
||||||
const resolvedMetadata: Directive = directiveResolver.resolve(type);
|
|
||||||
const selector = resolvedMetadata.selector;
|
|
||||||
|
|
||||||
return {
|
|
||||||
type,
|
|
||||||
selector,
|
|
||||||
inputs: parseFields(resolvedMetadata.inputs),
|
|
||||||
outputs: parseFields(resolvedMetadata.outputs)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseFields(bindings: string[]): PropertyBinding[] {
|
|
||||||
return (bindings || []).map(binding => new PropertyBinding(binding));
|
|
||||||
}
|
|
@ -6,17 +6,19 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {CssSelector, SelectorMatcher, createElementCssSelector} from '@angular/compiler';
|
import {DirectiveResolver} from '@angular/compiler';
|
||||||
import {Compiler, CompilerOptions, ComponentFactory, 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 {$$TESTABILITY, $COMPILE, $INJECTOR, $PARSE, $ROOT_SCOPE, COMPILER_KEY, COMPONENT_FACTORY_REF_MAP_KEY, INJECTOR_KEY, NG_ZONE_KEY, REQUIRE_INJECTOR, REQUIRE_NG_MODEL} from '../common/constants';
|
import {ComponentInfo} from '../common/component_info';
|
||||||
|
import {$$TESTABILITY, $COMPILE, $INJECTOR, $ROOT_SCOPE, COMPILER_KEY, INJECTOR_KEY, NG_ZONE_KEY} from '../common/constants';
|
||||||
|
import {ContentProjectionHelper} from '../common/content_projection_helper';
|
||||||
|
import {downgradeComponent} from '../common/downgrade_component';
|
||||||
import {downgradeInjectable} from '../common/downgrade_injectable';
|
import {downgradeInjectable} from '../common/downgrade_injectable';
|
||||||
import {Deferred, controllerKey, getAttributesAsArray, onError} from '../common/util';
|
import {Deferred, controllerKey, onError} from '../common/util';
|
||||||
|
|
||||||
import {DowngradeNg2ComponentAdapter} from './downgrade_ng2_adapter';
|
import {DynamicContentProjectionHelper} from './content_projection_helper';
|
||||||
import {ComponentInfo, getComponentInfo} from './metadata';
|
|
||||||
import {UpgradeNg1ComponentAdapterBuilder} from './upgrade_ng1_adapter';
|
import {UpgradeNg1ComponentAdapterBuilder} from './upgrade_ng1_adapter';
|
||||||
|
|
||||||
let upgradeCount: number = 0;
|
let upgradeCount: number = 0;
|
||||||
@ -102,7 +104,8 @@ let upgradeCount: number = 0;
|
|||||||
*/
|
*/
|
||||||
export class UpgradeAdapter {
|
export class UpgradeAdapter {
|
||||||
private idPrefix: string = `NG2_UPGRADE_${upgradeCount++}_`;
|
private idPrefix: string = `NG2_UPGRADE_${upgradeCount++}_`;
|
||||||
private upgradedComponents: Type<any>[] = [];
|
private directiveResolver: DirectiveResolver = new DirectiveResolver();
|
||||||
|
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.
|
||||||
*
|
*
|
||||||
@ -184,10 +187,13 @@ export class UpgradeAdapter {
|
|||||||
* });
|
* });
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
downgradeNg2Component(type: Type<any>): Function {
|
downgradeNg2Component(component: Type<any>): Function {
|
||||||
this.upgradedComponents.push(type);
|
this.downgradedComponents.push(component);
|
||||||
const info: ComponentInfo = getComponentInfo(type);
|
|
||||||
return ng1ComponentDirective(info, `${this.idPrefix}${info.selector}_c`);
|
const metadata: Directive = this.directiveResolver.resolve(component);
|
||||||
|
const info: ComponentInfo = {component, inputs: metadata.inputs, outputs: metadata.outputs};
|
||||||
|
|
||||||
|
return downgradeComponent(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -490,7 +496,6 @@ export class UpgradeAdapter {
|
|||||||
let original$applyFn: Function;
|
let original$applyFn: Function;
|
||||||
let rootScopePrototype: any;
|
let rootScopePrototype: any;
|
||||||
let rootScope: angular.IRootScopeService;
|
let rootScope: angular.IRootScopeService;
|
||||||
const componentFactoryRefMap: ComponentFactoryRefMap = {};
|
|
||||||
const upgradeAdapter = this;
|
const upgradeAdapter = this;
|
||||||
const ng1Module = this.ng1Module = angular.module(this.idPrefix, modules);
|
const ng1Module = this.ng1Module = angular.module(this.idPrefix, modules);
|
||||||
const platformRef = platformBrowserDynamic();
|
const platformRef = platformBrowserDynamic();
|
||||||
@ -499,7 +504,6 @@ export class UpgradeAdapter {
|
|||||||
this.ng2BootstrapDeferred = new Deferred();
|
this.ng2BootstrapDeferred = new Deferred();
|
||||||
ng1Module.factory(INJECTOR_KEY, () => this.moduleRef.injector.get(Injector))
|
ng1Module.factory(INJECTOR_KEY, () => this.moduleRef.injector.get(Injector))
|
||||||
.constant(NG_ZONE_KEY, this.ngZone)
|
.constant(NG_ZONE_KEY, this.ngZone)
|
||||||
.constant(COMPONENT_FACTORY_REF_MAP_KEY, componentFactoryRefMap)
|
|
||||||
.factory(COMPILER_KEY, () => this.moduleRef.injector.get(Compiler))
|
.factory(COMPILER_KEY, () => this.moduleRef.injector.get(Compiler))
|
||||||
.config([
|
.config([
|
||||||
'$provide', '$injector',
|
'$provide', '$injector',
|
||||||
@ -557,25 +561,18 @@ 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: ContentProjectionHelper, useClass: DynamicContentProjectionHelper},
|
||||||
this.upgradedProviders
|
this.upgradedProviders
|
||||||
],
|
],
|
||||||
imports: [this.ng2AppModule]
|
imports: [this.ng2AppModule],
|
||||||
|
entryComponents: this.downgradedComponents
|
||||||
}).Class({
|
}).Class({
|
||||||
constructor: function DynamicNgUpgradeModule() {},
|
constructor: function DynamicNgUpgradeModule() {},
|
||||||
ngDoBootstrap: function() {}
|
ngDoBootstrap: function() {}
|
||||||
});
|
});
|
||||||
(platformRef as any)
|
(platformRef as any)
|
||||||
._bootstrapModuleWithZone(
|
._bootstrapModuleWithZone(
|
||||||
DynamicNgUpgradeModule, this.compilerOptions, this.ngZone,
|
DynamicNgUpgradeModule, this.compilerOptions, this.ngZone)
|
||||||
(componentFactories: ComponentFactory<any>[]) => {
|
|
||||||
componentFactories.forEach((componentFactory) => {
|
|
||||||
const type: Type<any> = componentFactory.componentType;
|
|
||||||
if (this.upgradedComponents.indexOf(type) !== -1) {
|
|
||||||
componentFactoryRefMap[getComponentInfo(type).selector] =
|
|
||||||
componentFactory;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.then((ref: NgModuleRef<any>) => {
|
.then((ref: NgModuleRef<any>) => {
|
||||||
this.moduleRef = ref;
|
this.moduleRef = ref;
|
||||||
this.ngZone.run(() => {
|
this.ngZone.run(() => {
|
||||||
@ -603,10 +600,6 @@ export class UpgradeAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ComponentFactoryRefMap {
|
|
||||||
[selector: string]: ComponentFactory<any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Synchronous promise-like object to wrap parent injectors,
|
* Synchronous promise-like object to wrap parent injectors,
|
||||||
* to preserve the synchronous nature of AngularJS's $compile.
|
* to preserve the synchronous nature of AngularJS's $compile.
|
||||||
@ -644,88 +637,6 @@ class ParentInjectorPromise {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function ng1ComponentDirective(info: ComponentInfo, idPrefix: string): Function {
|
|
||||||
(<any>directiveFactory).$inject = [$INJECTOR, $COMPILE, COMPONENT_FACTORY_REF_MAP_KEY, $PARSE];
|
|
||||||
function directiveFactory(
|
|
||||||
ng1Injector: angular.IInjectorService, ng1Compile: angular.ICompileService,
|
|
||||||
componentFactoryRefMap: ComponentFactoryRefMap,
|
|
||||||
parse: angular.IParseService): angular.IDirective {
|
|
||||||
let idCount = 0;
|
|
||||||
let dashSelector = info.selector.replace(/[A-Z]/g, char => '-' + char.toLowerCase());
|
|
||||||
return {
|
|
||||||
restrict: 'E',
|
|
||||||
terminal: true,
|
|
||||||
require: [REQUIRE_INJECTOR, REQUIRE_NG_MODEL],
|
|
||||||
compile: (templateElement: angular.IAugmentedJQuery, templateAttributes: angular.IAttributes,
|
|
||||||
transclude: angular.ITranscludeFunction) => {
|
|
||||||
// We might have compile the contents lazily, because this might have been triggered by the
|
|
||||||
// UpgradeNg1ComponentAdapterBuilder, when the ng2 templates have not been compiled yet
|
|
||||||
return {
|
|
||||||
post: (scope: angular.IScope, element: angular.IAugmentedJQuery,
|
|
||||||
attrs: angular.IAttributes, required: any[],
|
|
||||||
transclude: angular.ITranscludeFunction): void => {
|
|
||||||
let id = idPrefix + (idCount++);
|
|
||||||
(<any>element[0]).id = id;
|
|
||||||
|
|
||||||
let parentInjector: Injector | ParentInjectorPromise = required[0];
|
|
||||||
let injectorPromise = new ParentInjectorPromise(element);
|
|
||||||
|
|
||||||
const ngModel: angular.INgModelController = required[1];
|
|
||||||
|
|
||||||
const ng2Compiler = ng1Injector.get(COMPILER_KEY) as Compiler;
|
|
||||||
const ngContentSelectors = ng2Compiler.getNgContentSelectors(info.type);
|
|
||||||
const linkFns = compileProjectedNodes(templateElement, ngContentSelectors);
|
|
||||||
|
|
||||||
const componentFactory: ComponentFactory<any> = componentFactoryRefMap[info.selector];
|
|
||||||
if (!componentFactory)
|
|
||||||
throw new Error('Expecting ComponentFactory for: ' + info.selector);
|
|
||||||
|
|
||||||
element.empty();
|
|
||||||
let projectableNodes = linkFns.map(link => {
|
|
||||||
let projectedClone: Node[];
|
|
||||||
link(scope, (clone: Node[]) => {
|
|
||||||
projectedClone = clone;
|
|
||||||
element.append(clone);
|
|
||||||
});
|
|
||||||
return projectedClone;
|
|
||||||
});
|
|
||||||
|
|
||||||
parentInjector = parentInjector || ng1Injector.get(INJECTOR_KEY);
|
|
||||||
|
|
||||||
if (parentInjector instanceof ParentInjectorPromise) {
|
|
||||||
parentInjector.then((resolvedInjector: Injector) => downgrade(resolvedInjector));
|
|
||||||
} else {
|
|
||||||
downgrade(parentInjector);
|
|
||||||
}
|
|
||||||
|
|
||||||
function downgrade(injector: Injector) {
|
|
||||||
const facade = new DowngradeNg2ComponentAdapter(
|
|
||||||
info, element, attrs, scope, ngModel, injector, parse, componentFactory);
|
|
||||||
facade.bootstrapNg2(projectableNodes);
|
|
||||||
facade.setupInputs();
|
|
||||||
facade.setupOutputs();
|
|
||||||
facade.registerCleanup();
|
|
||||||
injectorPromise.resolve(facade.componentRef.injector);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function compileProjectedNodes(
|
|
||||||
templateElement: angular.IAugmentedJQuery,
|
|
||||||
ngContentSelectors: string[]): angular.ILinkFn[] {
|
|
||||||
if (!ngContentSelectors)
|
|
||||||
throw new Error('Expecting ngContentSelectors for: ' + info.selector);
|
|
||||||
// We have to sort the projected content before we compile it, hence the terminal: true
|
|
||||||
let projectableTemplateNodes =
|
|
||||||
sortProjectableNodes(ngContentSelectors, templateElement.contents());
|
|
||||||
return projectableTemplateNodes.map(nodes => ng1Compile(nodes));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return directiveFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use `UpgradeAdapterRef` to control a hybrid AngularJS / Angular application.
|
* Use `UpgradeAdapterRef` to control a hybrid AngularJS / Angular application.
|
||||||
*
|
*
|
||||||
@ -766,36 +677,3 @@ export class UpgradeAdapterRef {
|
|||||||
this.ng2ModuleRef.destroy();
|
this.ng2ModuleRef.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort a set of DOM nodes that into groups based on the given content selectors
|
|
||||||
*/
|
|
||||||
export function sortProjectableNodes(ngContentSelectors: string[], childNodes: Node[]): Node[][] {
|
|
||||||
let projectableNodes: Node[][] = [];
|
|
||||||
let matcher = new SelectorMatcher();
|
|
||||||
let wildcardNgContentIndex: number;
|
|
||||||
for (let i = 0, ii = ngContentSelectors.length; i < ii; i++) {
|
|
||||||
projectableNodes[i] = [];
|
|
||||||
if (ngContentSelectors[i] === '*') {
|
|
||||||
wildcardNgContentIndex = i;
|
|
||||||
} else {
|
|
||||||
matcher.addSelectables(CssSelector.parse(ngContentSelectors[i]), i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (let node of childNodes) {
|
|
||||||
let ngContentIndices: number[] = [];
|
|
||||||
let selector =
|
|
||||||
createElementCssSelector(node.nodeName.toLowerCase(), getAttributesAsArray(node));
|
|
||||||
matcher.match(
|
|
||||||
selector, (selector, ngContentIndex) => { ngContentIndices.push(ngContentIndex); });
|
|
||||||
ngContentIndices.sort();
|
|
||||||
if (wildcardNgContentIndex !== undefined) {
|
|
||||||
ngContentIndices.push(wildcardNgContentIndex);
|
|
||||||
}
|
|
||||||
if (ngContentIndices.length > 0) {
|
|
||||||
projectableNodes[ngContentIndices[0]].push(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return projectableNodes;
|
|
||||||
}
|
|
||||||
|
@ -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 {ContentProjectionHelper} from '../common/content_projection_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, ContentProjectionHelper]})
|
||||||
export class UpgradeModule {
|
export class UpgradeModule {
|
||||||
/**
|
/**
|
||||||
* The AngularJS `$injector` for the upgrade application.
|
* The AngularJS `$injector` for the upgrade application.
|
||||||
|
@ -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([[]]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -1,65 +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 {Component} from '@angular/core';
|
|
||||||
import {getComponentInfo, parseFields} from '@angular/upgrade/src/dynamic/metadata';
|
|
||||||
|
|
||||||
export function main() {
|
|
||||||
describe('upgrade metadata', () => {
|
|
||||||
it('should extract component selector', () => {
|
|
||||||
expect(getComponentInfo(ElementNameComponent).selector).toBe('element-name-dashed');
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
describe('errors', () => {
|
|
||||||
it('should throw on missing selector', () => {
|
|
||||||
expect(() => getComponentInfo(NoAnnotationComponent))
|
|
||||||
.toThrowError('No Directive annotation found on NoAnnotationComponent');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('parseFields', () => {
|
|
||||||
it('should process nulls', () => { expect(parseFields(null)).toEqual([]); });
|
|
||||||
|
|
||||||
it('should process values', () => {
|
|
||||||
expect(parseFields([' name ', ' prop : attr '])).toEqual([
|
|
||||||
jasmine.objectContaining({
|
|
||||||
prop: 'name',
|
|
||||||
attr: 'name',
|
|
||||||
bracketAttr: '[name]',
|
|
||||||
parenAttr: '(name)',
|
|
||||||
bracketParenAttr: '[(name)]',
|
|
||||||
onAttr: 'onName',
|
|
||||||
bindAttr: 'bindName',
|
|
||||||
bindonAttr: 'bindonName'
|
|
||||||
}),
|
|
||||||
jasmine.objectContaining({
|
|
||||||
prop: 'prop',
|
|
||||||
attr: 'attr',
|
|
||||||
bracketAttr: '[attr]',
|
|
||||||
parenAttr: '(attr)',
|
|
||||||
bracketParenAttr: '[(attr)]',
|
|
||||||
onAttr: 'onAttr',
|
|
||||||
bindAttr: 'bindAttr',
|
|
||||||
bindonAttr: 'bindonAttr'
|
|
||||||
})
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({selector: 'element-name-dashed', template: ``})
|
|
||||||
class ElementNameComponent {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({selector: '[attr-name]', template: ``})
|
|
||||||
class AttributeNameComponent {
|
|
||||||
}
|
|
||||||
|
|
||||||
class NoAnnotationComponent {}
|
|
@ -7,3 +7,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export * from '../common/test_helpers';
|
export * from '../common/test_helpers';
|
||||||
|
|
||||||
|
export function nodes(html: string) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.innerHTML = html.trim();
|
||||||
|
return Array.prototype.slice.call(div.childNodes);
|
||||||
|
}
|
||||||
|
@ -11,7 +11,7 @@ import {async, fakeAsync, flushMicrotasks, tick} from '@angular/core/testing';
|
|||||||
import {BrowserModule} from '@angular/platform-browser';
|
import {BrowserModule} from '@angular/platform-browser';
|
||||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||||
import * as angular from '@angular/upgrade/src/common/angular1';
|
import * as angular from '@angular/upgrade/src/common/angular1';
|
||||||
import {UpgradeAdapter, UpgradeAdapterRef, sortProjectableNodes} from '@angular/upgrade/src/dynamic/upgrade_adapter';
|
import {UpgradeAdapter, UpgradeAdapterRef} from '@angular/upgrade/src/dynamic/upgrade_adapter';
|
||||||
import {html, multiTrim} from './test_helpers';
|
import {html, multiTrim} from './test_helpers';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
@ -95,9 +95,7 @@ export function main() {
|
|||||||
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||||
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
||||||
expect((platformRef as any)._bootstrapModuleWithZone)
|
expect((platformRef as any)._bootstrapModuleWithZone)
|
||||||
.toHaveBeenCalledWith(
|
.toHaveBeenCalledWith(jasmine.any(Function), {providers: []}, jasmine.any(Object));
|
||||||
jasmine.any(Function), {providers: []}, jasmine.any(Object),
|
|
||||||
jasmine.any(Function));
|
|
||||||
ref.dispose();
|
ref.dispose();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
@ -1876,73 +1874,4 @@ export function main() {
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('sortProjectableNodes', () => {
|
|
||||||
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 = sortProjectableNodes(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 = sortProjectableNodes(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 = sortProjectableNodes(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 = sortProjectableNodes([], contentNodes);
|
|
||||||
expect(noSelectorNodes).toEqual([]);
|
|
||||||
|
|
||||||
const noMatchSelectorNodes = sortProjectableNodes(['.not-there'], contentNodes);
|
|
||||||
expect(noMatchSelectorNodes).toEqual([[]]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function nodes(html: string) {
|
|
||||||
const element = document.createElement('div');
|
|
||||||
element.innerHTML = html;
|
|
||||||
return Array.prototype.slice.call(element.childNodes);
|
|
2
tools/public_api_guard/upgrade/index.d.ts
vendored
2
tools/public_api_guard/upgrade/index.d.ts
vendored
@ -2,7 +2,7 @@
|
|||||||
export declare class UpgradeAdapter {
|
export declare class UpgradeAdapter {
|
||||||
constructor(ng2AppModule: Type<any>, compilerOptions?: CompilerOptions);
|
constructor(ng2AppModule: Type<any>, compilerOptions?: CompilerOptions);
|
||||||
bootstrap(element: Element, modules?: any[], config?: angular.IAngularBootstrapConfig): UpgradeAdapterRef;
|
bootstrap(element: Element, modules?: any[], config?: angular.IAngularBootstrapConfig): UpgradeAdapterRef;
|
||||||
downgradeNg2Component(type: Type<any>): Function;
|
downgradeNg2Component(component: Type<any>): Function;
|
||||||
downgradeNg2Provider(token: any): Function;
|
downgradeNg2Provider(token: any): Function;
|
||||||
registerForNg1Tests(modules?: string[]): UpgradeAdapterRef;
|
registerForNg1Tests(modules?: string[]): UpgradeAdapterRef;
|
||||||
upgradeNg1Component(name: string): Type<any>;
|
upgradeNg1Component(name: string): Type<any>;
|
||||||
|
Reference in New Issue
Block a user