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:
parent
1367cd9569
commit
ea63676970
|
@ -309,23 +309,12 @@ export class PlatformRef_ extends PlatformRef {
|
|||
}
|
||||
|
||||
private _bootstrapModuleWithZone<M>(
|
||||
moduleType: Type<M>, compilerOptions: CompilerOptions|CompilerOptions[] = [], ngZone: NgZone,
|
||||
componentFactoryCallback?: any): Promise<NgModuleRef<M>> {
|
||||
moduleType: Type<M>, compilerOptions: CompilerOptions|CompilerOptions[] = [],
|
||||
ngZone: NgZone): Promise<NgModuleRef<M>> {
|
||||
const compilerFactory: CompilerFactory = this.injector.get(CompilerFactory);
|
||||
const compiler = compilerFactory.createCompiler(
|
||||
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)
|
||||
.then((moduleFactory) => this._bootstrapModuleFactoryWithZone(moduleFactory, ngZone));
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ export const $TEMPLATE_REQUEST = '$templateRequest';
|
|||
export const $$TESTABILITY = '$$testability';
|
||||
|
||||
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 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 {$COMPILE, $INJECTOR, $PARSE, INJECTOR_KEY, REQUIRE_INJECTOR, REQUIRE_NG_MODEL} from './constants';
|
||||
import {DowngradeComponentAdapter} from './downgrade_component_adapter';
|
||||
import {controllerKey} from './util';
|
||||
import {controllerKey, getComponentName} from './util';
|
||||
|
||||
let downgradeCount = 0;
|
||||
|
||||
|
@ -86,7 +86,8 @@ export function downgradeComponent(info: /* ComponentInfo */ {
|
|||
// triggered by `UpgradeNg1ComponentAdapterBuilder`, before the Angular templates have
|
||||
// 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 downgradeFn = (injector: Injector) => {
|
||||
|
@ -96,13 +97,14 @@ export function downgradeComponent(info: /* ComponentInfo */ {
|
|||
componentFactoryResolver.resolveComponentFactory(info.component);
|
||||
|
||||
if (!componentFactory) {
|
||||
throw new Error('Expecting ComponentFactory for: ' + info.component);
|
||||
throw new Error('Expecting ComponentFactory for: ' + getComponentName(info.component));
|
||||
}
|
||||
|
||||
const id = idPrefix + (idCount++);
|
||||
const injectorPromise = new ParentInjectorPromise(element);
|
||||
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();
|
||||
facade.createComponent(projectableNodes);
|
||||
|
|
|
@ -11,7 +11,8 @@ import {ChangeDetectorRef, ComponentFactory, ComponentRef, EventEmitter, Injecto
|
|||
import * as angular from './angular1';
|
||||
import {ComponentInfo, PropertyBinding} from './component_info';
|
||||
import {$SCOPE} from './constants';
|
||||
import {hookupNgModel} from './util';
|
||||
import {ContentProjectionHelper} from './content_projection_helper';
|
||||
import {getComponentName, hookupNgModel} from './util';
|
||||
|
||||
const INITIAL_VALUE = {
|
||||
__UNINITIALIZED__: true
|
||||
|
@ -29,24 +30,32 @@ export class DowngradeComponentAdapter {
|
|||
private id: string, private info: ComponentInfo, private element: angular.IAugmentedJQuery,
|
||||
private attrs: angular.IAttributes, private scope: angular.IScope,
|
||||
private ngModel: angular.INgModelController, private parentInjector: Injector,
|
||||
private $compile: angular.ICompileService, private $parse: angular.IParseService,
|
||||
private componentFactory: ComponentFactory<any>) {
|
||||
private $injector: angular.IInjectorService, private $compile: angular.ICompileService,
|
||||
private $parse: angular.IParseService, private componentFactory: ComponentFactory<any>) {
|
||||
(this.element[0] as any).id = id;
|
||||
this.componentScope = scope.$new();
|
||||
}
|
||||
|
||||
compileContents(): Node[][] {
|
||||
const projectableNodes: Node[][] = [];
|
||||
const linkFn = this.$compile(this.element.contents());
|
||||
const compiledProjectableNodes: Node[][] = [];
|
||||
|
||||
// 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();
|
||||
|
||||
linkFn(this.scope, (clone: Node[]) => {
|
||||
projectableNodes.push(clone);
|
||||
this.element.append(clone);
|
||||
linkFns.forEach(linkFn => {
|
||||
linkFn(this.scope, (clone: Node[]) => {
|
||||
compiledProjectableNodes.push(clone);
|
||||
this.element.append(clone);
|
||||
});
|
||||
});
|
||||
|
||||
return projectableNodes;
|
||||
return compiledProjectableNodes;
|
||||
}
|
||||
|
||||
createComponent(projectableNodes: Node[][]) {
|
||||
|
@ -162,7 +171,7 @@ export class DowngradeComponentAdapter {
|
|||
});
|
||||
} else {
|
||||
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
|
||||
*/
|
||||
|
||||
import * as angular from './angular_js';
|
||||
import {Type} from '@angular/core';
|
||||
import * as angular from './angular1';
|
||||
|
||||
export function onError(e: any) {
|
||||
// TODO: (misko): We seem to not have a stack trace here!
|
||||
|
@ -36,6 +37,11 @@ export function getAttributesAsArray(node: Node): [string, string][] {
|
|||
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> {
|
||||
promise: Promise<R>;
|
||||
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
|
||||
* ControlValueAccessor needed for AngularJS ng-model compatibility.
|
||||
* @return Whether the passed-in component implements the subset of the
|
||||
* `ControlValueAccessor` interface needed for AngularJS `ng-model`
|
||||
* compatibility.
|
||||
*/
|
||||
function supportsNgModel(component: any) {
|
||||
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
|
||||
* implements the needed subset of ControlValueAccessor.
|
||||
* Glue the AngularJS `NgModelController` (if it exists) to the component
|
||||
* (if it implements the needed subset of the `ControlValueAccessor` interface).
|
||||
*/
|
||||
export function hookupNgModel(ngModel: angular.INgModelController, component: any) {
|
||||
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
|
||||
*/
|
||||
|
||||
import {CssSelector, SelectorMatcher, createElementCssSelector} from '@angular/compiler';
|
||||
import {Compiler, CompilerOptions, ComponentFactory, Injector, NgModule, NgModuleRef, NgZone, Provider, Testability, Type} from '@angular/core';
|
||||
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 {$$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 {Deferred, controllerKey, getAttributesAsArray, onError} from '../common/util';
|
||||
import {Deferred, controllerKey, onError} from '../common/util';
|
||||
|
||||
import {DowngradeNg2ComponentAdapter} from './downgrade_ng2_adapter';
|
||||
import {ComponentInfo, getComponentInfo} from './metadata';
|
||||
import {DynamicContentProjectionHelper} from './content_projection_helper';
|
||||
import {UpgradeNg1ComponentAdapterBuilder} from './upgrade_ng1_adapter';
|
||||
|
||||
let upgradeCount: number = 0;
|
||||
|
@ -102,7 +104,8 @@ let upgradeCount: number = 0;
|
|||
*/
|
||||
export class UpgradeAdapter {
|
||||
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.
|
||||
*
|
||||
|
@ -184,10 +187,13 @@ export class UpgradeAdapter {
|
|||
* });
|
||||
* ```
|
||||
*/
|
||||
downgradeNg2Component(type: Type<any>): Function {
|
||||
this.upgradedComponents.push(type);
|
||||
const info: ComponentInfo = getComponentInfo(type);
|
||||
return ng1ComponentDirective(info, `${this.idPrefix}${info.selector}_c`);
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -490,7 +496,6 @@ export class UpgradeAdapter {
|
|||
let original$applyFn: Function;
|
||||
let rootScopePrototype: any;
|
||||
let rootScope: angular.IRootScopeService;
|
||||
const componentFactoryRefMap: ComponentFactoryRefMap = {};
|
||||
const upgradeAdapter = this;
|
||||
const ng1Module = this.ng1Module = angular.module(this.idPrefix, modules);
|
||||
const platformRef = platformBrowserDynamic();
|
||||
|
@ -499,7 +504,6 @@ export class UpgradeAdapter {
|
|||
this.ng2BootstrapDeferred = new Deferred();
|
||||
ng1Module.factory(INJECTOR_KEY, () => this.moduleRef.injector.get(Injector))
|
||||
.constant(NG_ZONE_KEY, this.ngZone)
|
||||
.constant(COMPONENT_FACTORY_REF_MAP_KEY, componentFactoryRefMap)
|
||||
.factory(COMPILER_KEY, () => this.moduleRef.injector.get(Compiler))
|
||||
.config([
|
||||
'$provide', '$injector',
|
||||
|
@ -557,25 +561,18 @@ export class UpgradeAdapter {
|
|||
providers: [
|
||||
{provide: $INJECTOR, useFactory: () => ng1Injector},
|
||||
{provide: $COMPILE, useFactory: () => ng1Injector.get($COMPILE)},
|
||||
{provide: ContentProjectionHelper, useClass: DynamicContentProjectionHelper},
|
||||
this.upgradedProviders
|
||||
],
|
||||
imports: [this.ng2AppModule]
|
||||
imports: [this.ng2AppModule],
|
||||
entryComponents: this.downgradedComponents
|
||||
}).Class({
|
||||
constructor: function DynamicNgUpgradeModule() {},
|
||||
ngDoBootstrap: function() {}
|
||||
});
|
||||
(platformRef as any)
|
||||
._bootstrapModuleWithZone(
|
||||
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;
|
||||
}
|
||||
});
|
||||
})
|
||||
DynamicNgUpgradeModule, this.compilerOptions, this.ngZone)
|
||||
.then((ref: NgModuleRef<any>) => {
|
||||
this.moduleRef = ref;
|
||||
this.ngZone.run(() => {
|
||||
|
@ -603,10 +600,6 @@ export class UpgradeAdapter {
|
|||
}
|
||||
}
|
||||
|
||||
interface ComponentFactoryRefMap {
|
||||
[selector: string]: ComponentFactory<any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronous promise-like object to wrap parent injectors,
|
||||
* 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.
|
||||
*
|
||||
|
@ -766,36 +677,3 @@ export class UpgradeAdapterRef {
|
|||
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 {$$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 {angular1Providers, setTempInjectorRef} from './angular1_providers';
|
||||
|
@ -129,7 +130,7 @@ import {angular1Providers, setTempInjectorRef} from './angular1_providers';
|
|||
*
|
||||
* @experimental
|
||||
*/
|
||||
@NgModule({providers: angular1Providers})
|
||||
@NgModule({providers: [angular1Providers, ContentProjectionHelper]})
|
||||
export class UpgradeModule {
|
||||
/**
|
||||
* 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 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 {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
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';
|
||||
|
||||
export function main() {
|
||||
|
@ -95,9 +95,7 @@ export function main() {
|
|||
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
||||
expect((platformRef as any)._bootstrapModuleWithZone)
|
||||
.toHaveBeenCalledWith(
|
||||
jasmine.any(Function), {providers: []}, jasmine.any(Object),
|
||||
jasmine.any(Function));
|
||||
.toHaveBeenCalledWith(jasmine.any(Function), {providers: []}, jasmine.any(Object));
|
||||
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,7 +2,7 @@
|
|||
export declare class UpgradeAdapter {
|
||||
constructor(ng2AppModule: Type<any>, compilerOptions?: CompilerOptions);
|
||||
bootstrap(element: Element, modules?: any[], config?: angular.IAngularBootstrapConfig): UpgradeAdapterRef;
|
||||
downgradeNg2Component(type: Type<any>): Function;
|
||||
downgradeNg2Component(component: Type<any>): Function;
|
||||
downgradeNg2Provider(token: any): Function;
|
||||
registerForNg1Tests(modules?: string[]): UpgradeAdapterRef;
|
||||
upgradeNg1Component(name: string): Type<any>;
|
||||
|
|
Loading…
Reference in New Issue