refactor(ngUpgrade): renames and docs
BREAKING CHANGE: - Changes the terminology to Adapter and upgrade/downgrade - Removes the Module from the public API to prevent confusion
This commit is contained in:
parent
d7ab5d44a5
commit
059e8faae2
|
@ -52,7 +52,7 @@ declare namespace angular {
|
||||||
assign(context: any, value: any): any;
|
assign(context: any, value: any): any;
|
||||||
}
|
}
|
||||||
function element(e: Element): IAugmentedJQuery;
|
function element(e: Element): IAugmentedJQuery;
|
||||||
function bootstrap(e: Element, modules: IModule[], config: IAngularBootstrapConfig);
|
function bootstrap(e: Element, modules: string[], config: IAngularBootstrapConfig);
|
||||||
|
|
||||||
namespace auto {
|
namespace auto {
|
||||||
interface IInjectorService {
|
interface IInjectorService {
|
||||||
|
|
|
@ -18,7 +18,7 @@ const INITIAL_VALUE = {
|
||||||
__UNINITIALIZED__: true
|
__UNINITIALIZED__: true
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Ng2ComponentFacade {
|
export class DowngradeNg2ComponentAdapter {
|
||||||
component: any = null;
|
component: any = null;
|
||||||
inputChangeCount: number = 0;
|
inputChangeCount: number = 0;
|
||||||
inputChanges: {[key: string]: SimpleChange} = null;
|
inputChanges: {[key: string]: SimpleChange} = null;
|
|
@ -0,0 +1,253 @@
|
||||||
|
///<reference path="./angular.d.ts"/>
|
||||||
|
|
||||||
|
import {
|
||||||
|
bind,
|
||||||
|
provide,
|
||||||
|
platform,
|
||||||
|
ApplicationRef,
|
||||||
|
AppViewManager,
|
||||||
|
Compiler,
|
||||||
|
Injector,
|
||||||
|
NgZone,
|
||||||
|
PlatformRef,
|
||||||
|
ProtoViewRef,
|
||||||
|
Type
|
||||||
|
} from 'angular2/angular2';
|
||||||
|
import {applicationDomBindings} from 'angular2/src/core/application_common';
|
||||||
|
import {applicationCommonBindings} from 'angular2/src/core/application_ref';
|
||||||
|
import {compilerProviders} from 'angular2/src/core/compiler/compiler';
|
||||||
|
|
||||||
|
import {getComponentInfo, ComponentInfo} from './metadata';
|
||||||
|
import {onError} from './util';
|
||||||
|
import {
|
||||||
|
NG1_COMPILE,
|
||||||
|
NG1_INJECTOR,
|
||||||
|
NG1_PARSE,
|
||||||
|
NG1_ROOT_SCOPE,
|
||||||
|
NG1_REQUIRE_INJECTOR_REF,
|
||||||
|
NG1_SCOPE,
|
||||||
|
NG2_APP_VIEW_MANAGER,
|
||||||
|
NG2_COMPILER,
|
||||||
|
NG2_INJECTOR,
|
||||||
|
NG2_PROTO_VIEW_REF_MAP,
|
||||||
|
NG2_ZONE,
|
||||||
|
REQUIRE_INJECTOR
|
||||||
|
} from './constants';
|
||||||
|
import {DowngradeNg2ComponentAdapter} from './downgrade_ng2_adapter';
|
||||||
|
import {UpgradeNg1ComponentAdapterBuilder} from './upgrade_ng1_adapter';
|
||||||
|
|
||||||
|
var upgradeCount: number = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use `UpgradeAdapter` to allow AngularJS v1 and Angular v2 to coexist in a single application.
|
||||||
|
*
|
||||||
|
* The `UpgradeAdapter` allows:
|
||||||
|
* 1. creation of Angular v2 component from AngularJS v1 component directive
|
||||||
|
* (See [UpgradeAdapter#upgradeNg1Component()])
|
||||||
|
* 2. creation of AngularJS v1 directive from Angular v2 component.
|
||||||
|
* (See [UpgradeAdapter#downgradeNg2Component()])
|
||||||
|
* 3. Bootstrapping of a hybrid Angular application which contains both of the frameworks
|
||||||
|
* coexisting in a single application.
|
||||||
|
*
|
||||||
|
* ## Mental Model
|
||||||
|
*
|
||||||
|
* When reasoning about how a hybrid application works it is useful to have a mental model which
|
||||||
|
* describes what is happening and explains what is happening at the lowest level.
|
||||||
|
*
|
||||||
|
* 1. There are two independent frameworks running in a single application, each framework treats
|
||||||
|
* the other as a black box.
|
||||||
|
* 2. Each DOM element on the page is owned exactly by one framework. Whichever framework
|
||||||
|
* instantiated the element is the owner. Each framework only updates/interacts with its own
|
||||||
|
* DOM elements and ignores others.
|
||||||
|
* 3. AngularJS v1 directives always execute inside AngularJS v1 framework codebase regardless of
|
||||||
|
* where they are instantiated.
|
||||||
|
* 4. Angular v2 components always execute inside Angular v2 framework codebase regardless of
|
||||||
|
* where they are instantiated.
|
||||||
|
* 5. An AngularJS v1 component can be upgraded to an Angular v2 component. This creates an
|
||||||
|
* Angular v2 directive, which bootstraps the AngularJS v1 component directive in that location.
|
||||||
|
* 6. An Angular v2 component can be downgraded to an AngularJS v1 component directive. This creates
|
||||||
|
* an AngularJS v1 directive, which bootstraps the Angular v2 component in that location.
|
||||||
|
* 7. Whenever an adapter component is instantiated the host element is owned by the the framework
|
||||||
|
* doing the instantiation. The other framework then instantiates and owns the view for that
|
||||||
|
* component. This implies that component bindings will always follow the semantics of the
|
||||||
|
* instantiation framework, but with Angular v2 syntax.
|
||||||
|
* 8. AngularJS v1 is always bootstrapped first and owns the bottom most view.
|
||||||
|
*
|
||||||
|
* ## Example
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* var adapter = new UpgradeAdapter();
|
||||||
|
* var module = angular.module('myExample', []);
|
||||||
|
*
|
||||||
|
* module.directive('ng1', function() {
|
||||||
|
* return {
|
||||||
|
* scope: { title: '@' },
|
||||||
|
* template: 'ng1[Hello {{title}}!](<span ng-transclude></span>)'
|
||||||
|
* };
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @Component({
|
||||||
|
* selector: 'ng2',
|
||||||
|
* inputs: ['name'],
|
||||||
|
* template: 'ng2[<ng1 [title]="name">transclude</ng1>](<ng-content></ng-content>)',
|
||||||
|
* directives: [adapter.upgradeNg1Component('ng1')]
|
||||||
|
* })
|
||||||
|
* class Ng2 {
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* document.body = '<ng2 name="World">project</ng2>';
|
||||||
|
*
|
||||||
|
* adapter.bootstrap(document.body, ['myExample']).ready(function() {
|
||||||
|
* expect(document.body.textContent).toEqual(
|
||||||
|
* "ng2[ng1[Hello World!](transclude)](project)");
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export class UpgradeAdapter {
|
||||||
|
/* @internal */
|
||||||
|
private idPrefix: string = `NG2_UPGRADE_${upgradeCount++}_`;
|
||||||
|
/* @internal */
|
||||||
|
private upgradedComponents: Type[] = [];
|
||||||
|
/* @internal */
|
||||||
|
private downgradedComponents: {[name: string]: UpgradeNg1ComponentAdapterBuilder} = {};
|
||||||
|
|
||||||
|
downgradeNg2Component(type: Type): Function {
|
||||||
|
this.upgradedComponents.push(type);
|
||||||
|
var info: ComponentInfo = getComponentInfo(type);
|
||||||
|
return ng1ComponentDirective(info, `${this.idPrefix}${info.selector}_c`);
|
||||||
|
}
|
||||||
|
|
||||||
|
upgradeNg1Component(name: string): Type {
|
||||||
|
if ((<any>this.downgradedComponents).hasOwnProperty(name)) {
|
||||||
|
return this.downgradedComponents[name].type;
|
||||||
|
} else {
|
||||||
|
return (this.downgradedComponents[name] = new UpgradeNg1ComponentAdapterBuilder(name)).type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bootstrap(element: Element, modules?: any[],
|
||||||
|
config?: angular.IAngularBootstrapConfig): UpgradeRef {
|
||||||
|
var upgrade = new UpgradeRef();
|
||||||
|
var ng1Injector: angular.auto.IInjectorService = null;
|
||||||
|
var platformRef: PlatformRef = platform();
|
||||||
|
var applicationRef: ApplicationRef = platformRef.application([
|
||||||
|
applicationCommonBindings(),
|
||||||
|
applicationDomBindings(),
|
||||||
|
compilerProviders(),
|
||||||
|
provide(NG1_INJECTOR, {useFactory: () => ng1Injector}),
|
||||||
|
provide(NG1_COMPILE, {useFactory: () => ng1Injector.get(NG1_COMPILE)})
|
||||||
|
]);
|
||||||
|
var injector: Injector = applicationRef.injector;
|
||||||
|
var ngZone: NgZone = injector.get(NgZone);
|
||||||
|
var compiler: Compiler = injector.get(Compiler);
|
||||||
|
var delayApplyExps: Function[] = [];
|
||||||
|
var original$applyFn: Function;
|
||||||
|
var rootScopePrototype: any;
|
||||||
|
var rootScope: angular.IRootScopeService;
|
||||||
|
var protoViewRefMap: ProtoViewRefMap = {};
|
||||||
|
var ng1Module = angular.module(this.idPrefix, modules);
|
||||||
|
ng1Module.value(NG2_INJECTOR, injector)
|
||||||
|
.value(NG2_ZONE, ngZone)
|
||||||
|
.value(NG2_COMPILER, compiler)
|
||||||
|
.value(NG2_PROTO_VIEW_REF_MAP, protoViewRefMap)
|
||||||
|
.value(NG2_APP_VIEW_MANAGER, injector.get(AppViewManager))
|
||||||
|
.config([
|
||||||
|
'$provide',
|
||||||
|
(provide) => {
|
||||||
|
provide.decorator(NG1_ROOT_SCOPE, [
|
||||||
|
'$delegate',
|
||||||
|
function(rootScopeDelegate: angular.IRootScopeService) {
|
||||||
|
rootScopePrototype = rootScopeDelegate.constructor.prototype;
|
||||||
|
if (rootScopePrototype.hasOwnProperty('$apply')) {
|
||||||
|
original$applyFn = rootScopePrototype.$apply;
|
||||||
|
rootScopePrototype.$apply = (exp) => delayApplyExps.push(exp);
|
||||||
|
} else {
|
||||||
|
throw new Error("Failed to find '$apply' on '$rootScope'!");
|
||||||
|
}
|
||||||
|
return rootScope = rootScopeDelegate;
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
])
|
||||||
|
.run([
|
||||||
|
'$injector',
|
||||||
|
'$rootScope',
|
||||||
|
(injector: angular.auto.IInjectorService, rootScope: angular.IRootScopeService) => {
|
||||||
|
ng1Injector = injector;
|
||||||
|
ngZone.overrideOnTurnDone(() => rootScope.$apply());
|
||||||
|
UpgradeNg1ComponentAdapterBuilder.resolve(this.downgradedComponents, injector);
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
angular.element(element).data(NG1_REQUIRE_INJECTOR_REF, injector);
|
||||||
|
ngZone.run(() => { angular.bootstrap(element, [this.idPrefix], config); });
|
||||||
|
this.compileNg2Components(compiler, protoViewRefMap)
|
||||||
|
.then((protoViewRefMap: ProtoViewRefMap) => {
|
||||||
|
ngZone.run(() => {
|
||||||
|
rootScopePrototype.$apply = original$applyFn; // restore original $apply
|
||||||
|
while (delayApplyExps.length) {
|
||||||
|
rootScope.$apply(delayApplyExps.shift());
|
||||||
|
}
|
||||||
|
upgrade.readyFn && upgrade.readyFn();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return upgrade;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* @internal */
|
||||||
|
private compileNg2Components(compiler: Compiler,
|
||||||
|
protoViewRefMap: ProtoViewRefMap): Promise<ProtoViewRefMap> {
|
||||||
|
var promises: Array<Promise<ProtoViewRef>> = [];
|
||||||
|
var types = this.upgradedComponents;
|
||||||
|
for (var i = 0; i < types.length; i++) {
|
||||||
|
promises.push(compiler.compileInHost(types[i]));
|
||||||
|
}
|
||||||
|
return Promise.all(promises).then((protoViews: Array<ProtoViewRef>) => {
|
||||||
|
var types = this.upgradedComponents;
|
||||||
|
for (var i = 0; i < protoViews.length; i++) {
|
||||||
|
protoViewRefMap[getComponentInfo(types[i]).selector] = protoViews[i];
|
||||||
|
}
|
||||||
|
return protoViewRefMap;
|
||||||
|
}, onError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProtoViewRefMap {
|
||||||
|
[selector: string]: ProtoViewRef
|
||||||
|
}
|
||||||
|
|
||||||
|
function ng1ComponentDirective(info: ComponentInfo, idPrefix: string): Function {
|
||||||
|
directiveFactory.$inject = [NG2_PROTO_VIEW_REF_MAP, NG2_APP_VIEW_MANAGER, NG1_PARSE];
|
||||||
|
function directiveFactory(protoViewRefMap: ProtoViewRefMap, viewManager: AppViewManager,
|
||||||
|
parse: angular.IParseService): angular.IDirective {
|
||||||
|
var protoView: ProtoViewRef = protoViewRefMap[info.selector];
|
||||||
|
if (!protoView) throw new Error('Expecting ProtoViewRef for: ' + info.selector);
|
||||||
|
var idCount = 0;
|
||||||
|
return {
|
||||||
|
restrict: 'E',
|
||||||
|
require: REQUIRE_INJECTOR,
|
||||||
|
link: {
|
||||||
|
post: (scope: angular.IScope, element: angular.IAugmentedJQuery, attrs: angular.IAttributes,
|
||||||
|
parentInjector: any, transclude: angular.ITranscludeFunction): void => {
|
||||||
|
var domElement = <any>element[0];
|
||||||
|
var facade = new DowngradeNg2ComponentAdapter(idPrefix + (idCount++), info, element,
|
||||||
|
attrs, scope, <Injector>parentInjector,
|
||||||
|
parse, viewManager, protoView);
|
||||||
|
facade.setupInputs();
|
||||||
|
facade.bootstrapNg2();
|
||||||
|
facade.projectContent();
|
||||||
|
facade.setupOutputs();
|
||||||
|
facade.registerCleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return directiveFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpgradeRef {
|
||||||
|
readyFn: Function;
|
||||||
|
|
||||||
|
ready(fn: Function) { this.readyFn = fn; }
|
||||||
|
}
|
|
@ -1,196 +0,0 @@
|
||||||
///<reference path="./angular.d.ts"/>
|
|
||||||
|
|
||||||
import {
|
|
||||||
bind,
|
|
||||||
provide,
|
|
||||||
platform,
|
|
||||||
ApplicationRef,
|
|
||||||
AppViewManager,
|
|
||||||
Compiler,
|
|
||||||
Injector,
|
|
||||||
NgZone,
|
|
||||||
PlatformRef,
|
|
||||||
ProtoViewRef,
|
|
||||||
Type
|
|
||||||
} from 'angular2/angular2';
|
|
||||||
import {applicationDomBindings} from 'angular2/src/core/application_common';
|
|
||||||
import {applicationCommonBindings} from 'angular2/src/core/application_ref';
|
|
||||||
import {compilerProviders} from 'angular2/src/core/compiler/compiler';
|
|
||||||
|
|
||||||
import {getComponentInfo, ComponentInfo} from './metadata';
|
|
||||||
import {onError} from './util';
|
|
||||||
import {
|
|
||||||
NG1_COMPILE,
|
|
||||||
NG1_INJECTOR,
|
|
||||||
NG1_PARSE,
|
|
||||||
NG1_ROOT_SCOPE,
|
|
||||||
NG1_REQUIRE_INJECTOR_REF,
|
|
||||||
NG1_SCOPE,
|
|
||||||
NG2_APP_VIEW_MANAGER,
|
|
||||||
NG2_COMPILER,
|
|
||||||
NG2_INJECTOR,
|
|
||||||
NG2_PROTO_VIEW_REF_MAP,
|
|
||||||
NG2_ZONE,
|
|
||||||
REQUIRE_INJECTOR
|
|
||||||
} from './constants';
|
|
||||||
import {Ng2ComponentFacade} from './ng2_facade';
|
|
||||||
import {ExportedNg1Component} from './ng1_facade';
|
|
||||||
|
|
||||||
var moduleCount: number = 0;
|
|
||||||
|
|
||||||
export function createUpgradeModule(): UpgradeModule {
|
|
||||||
var prefix = `NG2_UPGRADE_m${moduleCount++}_`;
|
|
||||||
return new UpgradeModule(prefix, angular.module(prefix, []));
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UpgradeModule {
|
|
||||||
importedNg2Components: Type[] = [];
|
|
||||||
exportedNg1Components: {[name: string]: ExportedNg1Component} = {}
|
|
||||||
|
|
||||||
constructor(public idPrefix: string, public ng1Module: angular.IModule) {}
|
|
||||||
|
|
||||||
importNg2Component(type: Type): UpgradeModule {
|
|
||||||
this.importedNg2Components.push(type);
|
|
||||||
var info: ComponentInfo = getComponentInfo(type);
|
|
||||||
var factory: Function = ng1ComponentDirective(info, `${this.idPrefix}${info.selector}_c`);
|
|
||||||
this.ng1Module.directive(info.selector, <any>factory);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
exportAsNg2Component(name: string): Type {
|
|
||||||
if ((<any>this.exportedNg1Components).hasOwnProperty(name)) {
|
|
||||||
return this.exportedNg1Components[name].type;
|
|
||||||
} else {
|
|
||||||
return (this.exportedNg1Components[name] = new ExportedNg1Component(name)).type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bootstrap(element: Element, modules?: any[],
|
|
||||||
config?: angular.IAngularBootstrapConfig): UpgradeRef {
|
|
||||||
var upgrade = new UpgradeRef();
|
|
||||||
var ng1Injector: angular.auto.IInjectorService = null;
|
|
||||||
var bindings = [
|
|
||||||
applicationCommonBindings(),
|
|
||||||
applicationDomBindings(),
|
|
||||||
compilerProviders(),
|
|
||||||
provide(NG1_INJECTOR, {useFactory: () => ng1Injector}),
|
|
||||||
provide(NG1_COMPILE, {useFactory: () => ng1Injector.get(NG1_COMPILE)})
|
|
||||||
];
|
|
||||||
|
|
||||||
var platformRef: PlatformRef = platform();
|
|
||||||
var applicationRef: ApplicationRef = platformRef.application(bindings);
|
|
||||||
var injector: Injector = applicationRef.injector;
|
|
||||||
var ngZone: NgZone = injector.get(NgZone);
|
|
||||||
var compiler: Compiler = injector.get(Compiler);
|
|
||||||
var delayApplyExps: Function[] = [];
|
|
||||||
var original$applyFn: Function;
|
|
||||||
var rootScopePrototype: any;
|
|
||||||
var rootScope: angular.IRootScopeService;
|
|
||||||
var protoViewRefMap: ProtoViewRefMap = {};
|
|
||||||
ngZone.run(() => {
|
|
||||||
this.ng1Module.value(NG2_INJECTOR, injector)
|
|
||||||
.value(NG2_ZONE, ngZone)
|
|
||||||
.value(NG2_COMPILER, compiler)
|
|
||||||
.value(NG2_PROTO_VIEW_REF_MAP, protoViewRefMap)
|
|
||||||
.value(NG2_APP_VIEW_MANAGER, injector.get(AppViewManager))
|
|
||||||
.config([
|
|
||||||
'$provide',
|
|
||||||
(provide) => {
|
|
||||||
provide.decorator(NG1_ROOT_SCOPE, [
|
|
||||||
'$delegate',
|
|
||||||
function(rootScopeDelegate: angular.IRootScopeService) {
|
|
||||||
rootScopePrototype = rootScopeDelegate.constructor.prototype;
|
|
||||||
if (rootScopePrototype.hasOwnProperty('$apply')) {
|
|
||||||
original$applyFn = rootScopePrototype.$apply;
|
|
||||||
rootScopePrototype.$apply = (exp) => delayApplyExps.push(exp);
|
|
||||||
} else {
|
|
||||||
throw new Error("Failed to find '$apply' on '$rootScope'!");
|
|
||||||
}
|
|
||||||
return rootScope = rootScopeDelegate;
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
])
|
|
||||||
.run([
|
|
||||||
'$injector',
|
|
||||||
'$rootScope',
|
|
||||||
(injector: angular.auto.IInjectorService, rootScope: angular.IRootScopeService) => {
|
|
||||||
ng1Injector = injector;
|
|
||||||
ngZone.overrideOnTurnDone(() => rootScope.$apply());
|
|
||||||
ExportedNg1Component.resolve(this.exportedNg1Components, injector);
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
modules = modules ? [].concat(modules) : [];
|
|
||||||
modules.push(this.idPrefix);
|
|
||||||
angular.element(element).data(NG1_REQUIRE_INJECTOR_REF, injector);
|
|
||||||
angular.bootstrap(element, modules, config);
|
|
||||||
});
|
|
||||||
this.compileNg2Components(compiler, protoViewRefMap)
|
|
||||||
.then((protoViewRefMap: ProtoViewRefMap) => {
|
|
||||||
ngZone.run(() => {
|
|
||||||
rootScopePrototype.$apply = original$applyFn; // restore original $apply
|
|
||||||
while (delayApplyExps.length) {
|
|
||||||
rootScope.$apply(delayApplyExps.shift());
|
|
||||||
}
|
|
||||||
upgrade.readyFn && upgrade.readyFn();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return upgrade;
|
|
||||||
}
|
|
||||||
|
|
||||||
private compileNg2Components(compiler: Compiler,
|
|
||||||
protoViewRefMap: ProtoViewRefMap): Promise<ProtoViewRefMap> {
|
|
||||||
var promises: Array<Promise<ProtoViewRef>> = [];
|
|
||||||
var types = this.importedNg2Components;
|
|
||||||
for (var i = 0; i < types.length; i++) {
|
|
||||||
promises.push(compiler.compileInHost(types[i]));
|
|
||||||
}
|
|
||||||
return Promise.all(promises).then((protoViews: Array<ProtoViewRef>) => {
|
|
||||||
var types = this.importedNg2Components;
|
|
||||||
for (var i = 0; i < protoViews.length; i++) {
|
|
||||||
protoViewRefMap[getComponentInfo(types[i]).selector] = protoViews[i];
|
|
||||||
}
|
|
||||||
return protoViewRefMap;
|
|
||||||
}, onError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ProtoViewRefMap {
|
|
||||||
[selector: string]: ProtoViewRef
|
|
||||||
}
|
|
||||||
|
|
||||||
function ng1ComponentDirective(info: ComponentInfo, idPrefix: string): Function {
|
|
||||||
directiveFactory.$inject = [NG2_PROTO_VIEW_REF_MAP, NG2_APP_VIEW_MANAGER, NG1_PARSE];
|
|
||||||
function directiveFactory(protoViewRefMap: ProtoViewRefMap, viewManager: AppViewManager,
|
|
||||||
parse: angular.IParseService): angular.IDirective {
|
|
||||||
var protoView: ProtoViewRef = protoViewRefMap[info.selector];
|
|
||||||
if (!protoView) throw new Error('Expecting ProtoViewRef for: ' + info.selector);
|
|
||||||
var idCount = 0;
|
|
||||||
return {
|
|
||||||
restrict: 'E',
|
|
||||||
require: REQUIRE_INJECTOR,
|
|
||||||
link: {
|
|
||||||
post: (scope: angular.IScope, element: angular.IAugmentedJQuery, attrs: angular.IAttributes,
|
|
||||||
parentInjector: any, transclude: angular.ITranscludeFunction): void => {
|
|
||||||
var domElement = <any>element[0];
|
|
||||||
var facade =
|
|
||||||
new Ng2ComponentFacade(idPrefix + (idCount++), info, element, attrs, scope,
|
|
||||||
<Injector>parentInjector, parse, viewManager, protoView);
|
|
||||||
facade.setupInputs();
|
|
||||||
facade.bootstrapNg2();
|
|
||||||
facade.projectContent();
|
|
||||||
facade.setupOutputs();
|
|
||||||
facade.registerCleanup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return directiveFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UpgradeRef {
|
|
||||||
readyFn: Function;
|
|
||||||
|
|
||||||
ready(fn: Function) { this.readyFn = fn; }
|
|
||||||
}
|
|
|
@ -16,7 +16,7 @@ const INITIAL_VALUE = {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export class ExportedNg1Component {
|
export class UpgradeNg1ComponentAdapterBuilder {
|
||||||
type: Type;
|
type: Type;
|
||||||
inputs: string[] = [];
|
inputs: string[] = [];
|
||||||
inputsRename: string[] = [];
|
inputsRename: string[] = [];
|
||||||
|
@ -38,7 +38,7 @@ export class ExportedNg1Component {
|
||||||
ElementRef,
|
ElementRef,
|
||||||
function(compile: angular.ICompileService, scope: angular.IScope,
|
function(compile: angular.ICompileService, scope: angular.IScope,
|
||||||
elementRef: ElementRef) {
|
elementRef: ElementRef) {
|
||||||
return new Ng1ComponentFacade(compile, scope, elementRef, self.inputs,
|
return new UpgradeNg1ComponentAdapter(compile, scope, elementRef, self.inputs,
|
||||||
self.outputs, self.propertyOutputs,
|
self.outputs, self.propertyOutputs,
|
||||||
self.checkProperties, self.propertyMap);
|
self.checkProperties, self.propertyMap);
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,7 @@ export class ExportedNg1Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static resolve(exportedComponents: {[name: string]: ExportedNg1Component},
|
static resolve(exportedComponents: {[name: string]: UpgradeNg1ComponentAdapterBuilder},
|
||||||
injector: angular.auto.IInjectorService) {
|
injector: angular.auto.IInjectorService) {
|
||||||
for (var name in exportedComponents) {
|
for (var name in exportedComponents) {
|
||||||
if ((<any>exportedComponents).hasOwnProperty(name)) {
|
if ((<any>exportedComponents).hasOwnProperty(name)) {
|
||||||
|
@ -105,7 +105,7 @@ export class ExportedNg1Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Ng1ComponentFacade implements OnChanges, DoCheck {
|
class UpgradeNg1ComponentAdapter implements OnChanges, DoCheck {
|
||||||
componentScope: angular.IScope = null;
|
componentScope: angular.IScope = null;
|
||||||
checkLastValues: any[] = [];
|
checkLastValues: any[] = [];
|
||||||
|
|
|
@ -11,24 +11,25 @@ import {
|
||||||
xit,
|
xit,
|
||||||
} from 'angular2/testing_internal';
|
} from 'angular2/testing_internal';
|
||||||
|
|
||||||
import {Component, View, Inject, EventEmitter} from 'angular2/angular2';
|
import {Component, Inject, EventEmitter} from 'angular2/angular2';
|
||||||
import {createUpgradeModule, UpgradeModule} from 'upgrade/upgrade';
|
import {UpgradeAdapter} from 'upgrade/upgrade';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('upgrade: ng1 to ng2', () => {
|
describe('adapter: ng1 to ng2', () => {
|
||||||
it('should have angular 1 loaded', () => expect(angular.version.major).toBe(1));
|
it('should have angular 1 loaded', () => expect(angular.version.major).toBe(1));
|
||||||
|
|
||||||
it('should instantiate ng2 in ng1 template and project content',
|
it('should instantiate ng2 in ng1 template and project content',
|
||||||
inject([AsyncTestCompleter], (async) => {
|
inject([AsyncTestCompleter], (async) => {
|
||||||
var Ng2 = Component({selector: 'ng2'})
|
var ng1Module = angular.module('ng1', []);
|
||||||
.View({template: `{{ 'NG2' }}(<ng-content></ng-content>)`})
|
var Ng2 = Component({selector: 'ng2', template: `{{ 'NG2' }}(<ng-content></ng-content>)`})
|
||||||
.Class({constructor: function() {}});
|
.Class({constructor: function() {}});
|
||||||
|
|
||||||
var element = html("<div>{{ 'ng1[' }}<ng2>~{{ 'ng-content' }}~</ng2>{{ ']' }}</div>");
|
var element = html("<div>{{ 'ng1[' }}<ng2>~{{ 'ng-content' }}~</ng2>{{ ']' }}</div>");
|
||||||
|
|
||||||
var upgradeModule: UpgradeModule = createUpgradeModule();
|
var adapter: UpgradeAdapter = new UpgradeAdapter();
|
||||||
upgradeModule.importNg2Component(Ng2);
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||||
upgradeModule.bootstrap(element).ready(() => {
|
adapter.bootstrap(element, ['ng1'])
|
||||||
|
.ready(() => {
|
||||||
expect(document.body.textContent).toEqual("ng1[NG2(~ng-content~)]");
|
expect(document.body.textContent).toEqual("ng1[NG2(~ng-content~)]");
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
|
@ -36,23 +37,24 @@ export function main() {
|
||||||
|
|
||||||
it('should instantiate ng1 in ng2 template and project content',
|
it('should instantiate ng1 in ng2 template and project content',
|
||||||
inject([AsyncTestCompleter], (async) => {
|
inject([AsyncTestCompleter], (async) => {
|
||||||
var upgrMod: UpgradeModule = createUpgradeModule();
|
var adapter: UpgradeAdapter = new UpgradeAdapter();
|
||||||
|
var ng1Module = angular.module('ng1', []);
|
||||||
|
|
||||||
var Ng2 = Component({selector: 'ng2-1'})
|
var Ng2 = Component({
|
||||||
.View({
|
selector: 'ng2',
|
||||||
template: `{{ 'ng2(' }}<ng1>{{'transclude'}}</ng1>{{ ')' }}`,
|
template: `{{ 'ng2(' }}<ng1>{{'transclude'}}</ng1>{{ ')' }}`,
|
||||||
directives: [upgrMod.exportAsNg2Component('ng1')]
|
directives: [adapter.upgradeNg1Component('ng1')]
|
||||||
})
|
}).Class({constructor: function() {}});
|
||||||
.Class({constructor: function() {}});
|
|
||||||
|
|
||||||
upgrMod.ng1Module.directive('ng1', () => {
|
ng1Module.directive('ng1', () => {
|
||||||
return {transclude: true, template: '{{ "ng1" }}(<ng-transclude></ng-transclude>)'};
|
return {transclude: true, template: '{{ "ng1" }}(<ng-transclude></ng-transclude>)'};
|
||||||
});
|
});
|
||||||
upgrMod.importNg2Component(Ng2);
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||||
|
|
||||||
var element = html("<div>{{'ng1('}}<ng2-1></ng2-1>{{')'}}</div>");
|
var element = html("<div>{{'ng1('}}<ng2></ng2>{{')'}}</div>");
|
||||||
|
|
||||||
upgrMod.bootstrap(element).ready(() => {
|
adapter.bootstrap(element, ['ng1'])
|
||||||
|
.ready(() => {
|
||||||
expect(document.body.textContent).toEqual("ng1(ng2(ng1(transclude)))");
|
expect(document.body.textContent).toEqual("ng1(ng2(ng1(transclude)))");
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
|
@ -61,33 +63,34 @@ export function main() {
|
||||||
describe('scope/component change-detection', () => {
|
describe('scope/component change-detection', () => {
|
||||||
it('should interleave scope and component expressions',
|
it('should interleave scope and component expressions',
|
||||||
inject([AsyncTestCompleter], (async) => {
|
inject([AsyncTestCompleter], (async) => {
|
||||||
|
var ng1Module = angular.module('ng1', []);
|
||||||
var log = [];
|
var log = [];
|
||||||
var l = function(value) {
|
var l = function(value) {
|
||||||
log.push(value);
|
log.push(value);
|
||||||
return value + ';';
|
return value + ';';
|
||||||
};
|
};
|
||||||
var upgrMod: UpgradeModule = createUpgradeModule();
|
var adapter: UpgradeAdapter = new UpgradeAdapter();
|
||||||
|
|
||||||
upgrMod.ng1Module.directive('ng1a', () => { return {template: "{{ l('ng1a') }}"}; });
|
ng1Module.directive('ng1a', () => { return {template: "{{ l('ng1a') }}"}; });
|
||||||
upgrMod.ng1Module.directive('ng1b', () => { return {template: "{{ l('ng1b') }}"}; });
|
ng1Module.directive('ng1b', () => { return {template: "{{ l('ng1b') }}"}; });
|
||||||
upgrMod.ng1Module.run(($rootScope) => {
|
ng1Module.run(($rootScope) => {
|
||||||
$rootScope.l = l;
|
$rootScope.l = l;
|
||||||
$rootScope.reset = () => log.length = 0;
|
$rootScope.reset = () => log.length = 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
upgrMod.importNg2Component(
|
var Ng2 =
|
||||||
Component({selector: 'ng2'})
|
Component({
|
||||||
.View({
|
selector: 'ng2',
|
||||||
template: `{{l('2A')}}<ng1a></ng1a>{{l('2B')}}<ng1b></ng1b>{{l('2C')}}`,
|
template: `{{l('2A')}}<ng1a></ng1a>{{l('2B')}}<ng1b></ng1b>{{l('2C')}}`,
|
||||||
directives: [
|
directives:
|
||||||
upgrMod.exportAsNg2Component('ng1a'),
|
[adapter.upgradeNg1Component('ng1a'), adapter.upgradeNg1Component('ng1b')]
|
||||||
upgrMod.exportAsNg2Component('ng1b')
|
}).Class({constructor: function() { this.l = l; }});
|
||||||
]
|
|
||||||
})
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||||
.Class({constructor: function() { this.l = l; }}));
|
|
||||||
|
|
||||||
var element = html("<div>{{reset(); l('1A');}}<ng2>{{l('1B')}}</ng2>{{l('1C')}}</div>");
|
var element = html("<div>{{reset(); l('1A');}}<ng2>{{l('1B')}}</ng2>{{l('1C')}}</div>");
|
||||||
upgrMod.bootstrap(element).ready(() => {
|
adapter.bootstrap(element, ['ng1'])
|
||||||
|
.ready(() => {
|
||||||
expect(document.body.textContent).toEqual("1A;2A;ng1a;2B;ng1b;2C;1C;");
|
expect(document.body.textContent).toEqual("1A;2A;ng1a;2B;ng1b;2C;1C;");
|
||||||
// https://github.com/angular/angular.js/issues/12983
|
// https://github.com/angular/angular.js/issues/12983
|
||||||
expect(log).toEqual(['1A', '1B', '1C', '2A', '2B', '2C', 'ng1a', 'ng1b']);
|
expect(log).toEqual(['1A', '1B', '1C', '2A', '2B', '2C', 'ng1a', 'ng1b']);
|
||||||
|
@ -98,8 +101,10 @@ export function main() {
|
||||||
|
|
||||||
describe('binding from ng1 to ng2', () => {
|
describe('binding from ng1 to ng2', () => {
|
||||||
it('should bind properties, events', inject([AsyncTestCompleter], (async) => {
|
it('should bind properties, events', inject([AsyncTestCompleter], (async) => {
|
||||||
var upgrMod: UpgradeModule = createUpgradeModule();
|
var adapter: UpgradeAdapter = new UpgradeAdapter();
|
||||||
upgrMod.ng1Module.run(($rootScope) => {
|
var ng1Module = angular.module('ng1', []);
|
||||||
|
|
||||||
|
ng1Module.run(($rootScope) => {
|
||||||
$rootScope.dataA = 'A';
|
$rootScope.dataA = 'A';
|
||||||
$rootScope.dataB = 'B';
|
$rootScope.dataB = 'B';
|
||||||
$rootScope.modelA = 'initModelA';
|
$rootScope.modelA = 'initModelA';
|
||||||
|
@ -107,7 +112,7 @@ export function main() {
|
||||||
$rootScope.eventA = '?';
|
$rootScope.eventA = '?';
|
||||||
$rootScope.eventB = '?';
|
$rootScope.eventB = '?';
|
||||||
});
|
});
|
||||||
upgrMod.importNg2Component(
|
var Ng2 =
|
||||||
Component({
|
Component({
|
||||||
selector: 'ng2',
|
selector: 'ng2',
|
||||||
inputs: ['literal', 'interpolate', 'oneWayA', 'oneWayB', 'twoWayA', 'twoWayB'],
|
inputs: ['literal', 'interpolate', 'oneWayA', 'oneWayB', 'twoWayA', 'twoWayB'],
|
||||||
|
@ -116,11 +121,8 @@ export function main() {
|
||||||
'eventB',
|
'eventB',
|
||||||
'twoWayAEmitter: twoWayAChange',
|
'twoWayAEmitter: twoWayAChange',
|
||||||
'twoWayBEmitter: twoWayBChange'
|
'twoWayBEmitter: twoWayBChange'
|
||||||
]
|
],
|
||||||
})
|
template: "ignore: {{ignore}}; " +
|
||||||
.View({
|
|
||||||
template:
|
|
||||||
"ignore: {{ignore}}; " +
|
|
||||||
"literal: {{literal}}; interpolate: {{interpolate}}; " +
|
"literal: {{literal}}; interpolate: {{interpolate}}; " +
|
||||||
"oneWayA: {{oneWayA}}; oneWayB: {{oneWayB}}; " +
|
"oneWayA: {{oneWayA}}; oneWayB: {{oneWayB}}; " +
|
||||||
"twoWayA: {{twoWayA}}; twoWayB: {{twoWayB}}; ({{onChangesCount}})"
|
"twoWayA: {{twoWayA}}; twoWayB: {{twoWayB}}; ({{onChangesCount}})"
|
||||||
|
@ -185,7 +187,8 @@ export function main() {
|
||||||
throw new Error('Called too many times! ' + JSON.stringify(changes));
|
throw new Error('Called too many times! ' + JSON.stringify(changes));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
});
|
||||||
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||||
var element = html(`<div>
|
var element = html(`<div>
|
||||||
<ng2 literal="Text" interpolate="Hello {{'world'}}"
|
<ng2 literal="Text" interpolate="Hello {{'world'}}"
|
||||||
bind-one-way-a="dataA" [one-way-b]="dataB"
|
bind-one-way-a="dataA" [one-way-b]="dataB"
|
||||||
|
@ -193,7 +196,8 @@ export function main() {
|
||||||
on-event-a='eventA=$event' (event-b)="eventB=$event"></ng2>
|
on-event-a='eventA=$event' (event-b)="eventB=$event"></ng2>
|
||||||
| modelA: {{modelA}}; modelB: {{modelB}}; eventA: {{eventA}}; eventB: {{eventB}};
|
| modelA: {{modelA}}; modelB: {{modelB}}; eventA: {{eventA}}; eventB: {{eventB}};
|
||||||
</div>`);
|
</div>`);
|
||||||
upgrMod.bootstrap(element).ready(() => {
|
adapter.bootstrap(element, ['ng1'])
|
||||||
|
.ready(() => {
|
||||||
expect(multiTrim(document.body.textContent))
|
expect(multiTrim(document.body.textContent))
|
||||||
.toEqual(
|
.toEqual(
|
||||||
"ignore: -; " + "literal: Text; interpolate: Hello world; " +
|
"ignore: -; " + "literal: Text; interpolate: Hello world; " +
|
||||||
|
@ -215,7 +219,9 @@ export function main() {
|
||||||
|
|
||||||
describe('binding from ng2 to ng1', () => {
|
describe('binding from ng2 to ng1', () => {
|
||||||
it('should bind properties, events', inject([AsyncTestCompleter], (async) => {
|
it('should bind properties, events', inject([AsyncTestCompleter], (async) => {
|
||||||
var upgrMod = createUpgradeModule();
|
var adapter = new UpgradeAdapter();
|
||||||
|
var ng1Module = angular.module('ng1', []);
|
||||||
|
|
||||||
var ng1 = function() {
|
var ng1 = function() {
|
||||||
return {
|
return {
|
||||||
template: 'Hello {{fullName}}; A: {{dataA}}; B: {{dataB}}; | ',
|
template: 'Hello {{fullName}}; A: {{dataA}}; B: {{dataB}}; | ',
|
||||||
|
@ -233,16 +239,16 @@ export function main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
upgrMod.ng1Module.directive('ng1', ng1);
|
ng1Module.directive('ng1', ng1);
|
||||||
var ng2 =
|
var Ng2 =
|
||||||
Component({selector: 'ng2'})
|
Component({
|
||||||
.View({
|
selector: 'ng2',
|
||||||
template:
|
template:
|
||||||
'<ng1 full-name="{{last}}, {{first}}" [model-a]="first" [(model-b)]="last" ' +
|
'<ng1 full-name="{{last}}, {{first}}" [model-a]="first" [(model-b)]="last" ' +
|
||||||
'(event)="event=$event"></ng1>' +
|
'(event)="event=$event"></ng1>' +
|
||||||
'<ng1 full-name="{{\'TEST\'}}" model-a="First" model-b="Last"></ng1>' +
|
'<ng1 full-name="{{\'TEST\'}}" model-a="First" model-b="Last"></ng1>' +
|
||||||
'{{event}}-{{last}}, {{first}}',
|
'{{event}}-{{last}}, {{first}}',
|
||||||
directives: [upgrMod.exportAsNg2Component('ng1')]
|
directives: [adapter.upgradeNg1Component('ng1')]
|
||||||
})
|
})
|
||||||
.Class({
|
.Class({
|
||||||
constructor: function() {
|
constructor: function() {
|
||||||
|
@ -251,9 +257,10 @@ export function main() {
|
||||||
this.event = '?';
|
this.event = '?';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
upgrMod.importNg2Component(ng2);
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||||
var element = html(`<div><ng2></ng2></div>`);
|
var element = html(`<div><ng2></ng2></div>`);
|
||||||
upgrMod.bootstrap(element).ready(() => {
|
adapter.bootstrap(element, ['ng1'])
|
||||||
|
.ready(() => {
|
||||||
// we need to do setTimeout, because the EventEmitter uses setTimeout to schedule
|
// we need to do setTimeout, because the EventEmitter uses setTimeout to schedule
|
||||||
// events, and so without this we would not see the events processed.
|
// events, and so without this we would not see the events processed.
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
@ -266,6 +273,40 @@ export function main() {
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('examples', () => {
|
||||||
|
it('should verify UpgradeAdapter example', inject([AsyncTestCompleter], (async) => {
|
||||||
|
var adapter = new UpgradeAdapter();
|
||||||
|
var module = angular.module('myExample', []);
|
||||||
|
|
||||||
|
module.directive('ng1', function() {
|
||||||
|
return {
|
||||||
|
scope: {title: '@'},
|
||||||
|
transclude: true, template: 'ng1[Hello {{title}}!](<span ng-transclude></span>)'
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
var Ng2 =
|
||||||
|
Component({
|
||||||
|
selector: 'ng2',
|
||||||
|
inputs: ['name'],
|
||||||
|
template: 'ng2[<ng1 [title]="name">transclude</ng1>](<ng-content></ng-content>)',
|
||||||
|
directives: [adapter.upgradeNg1Component('ng1')]
|
||||||
|
}).Class({constructor: function() {}});
|
||||||
|
|
||||||
|
module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||||
|
|
||||||
|
document.body.innerHTML = '<ng2 name="World">project</ng2>';
|
||||||
|
|
||||||
|
adapter.bootstrap(document.body, ['myExample'])
|
||||||
|
.ready(function() {
|
||||||
|
expect(multiTrim(document.body.textContent))
|
||||||
|
.toEqual("ng2[ng1[Hello World!](transclude)](project)");
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
export {createUpgradeModule, UpgradeModule} from './src/upgrade_module';
|
export {UpgradeAdapter} from './src/upgrade_adapter';
|
||||||
|
|
Loading…
Reference in New Issue