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;
|
||||
}
|
||||
function element(e: Element): IAugmentedJQuery;
|
||||
function bootstrap(e: Element, modules: IModule[], config: IAngularBootstrapConfig);
|
||||
function bootstrap(e: Element, modules: string[], config: IAngularBootstrapConfig);
|
||||
|
||||
namespace auto {
|
||||
interface IInjectorService {
|
||||
|
|
|
@ -18,7 +18,7 @@ const INITIAL_VALUE = {
|
|||
__UNINITIALIZED__: true
|
||||
};
|
||||
|
||||
export class Ng2ComponentFacade {
|
||||
export class DowngradeNg2ComponentAdapter {
|
||||
component: any = null;
|
||||
inputChangeCount: number = 0;
|
||||
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;
|
||||
inputs: string[] = [];
|
||||
inputsRename: string[] = [];
|
||||
|
@ -38,9 +38,9 @@ export class ExportedNg1Component {
|
|||
ElementRef,
|
||||
function(compile: angular.ICompileService, scope: angular.IScope,
|
||||
elementRef: ElementRef) {
|
||||
return new Ng1ComponentFacade(compile, scope, elementRef, self.inputs,
|
||||
self.outputs, self.propertyOutputs,
|
||||
self.checkProperties, self.propertyMap);
|
||||
return new UpgradeNg1ComponentAdapter(compile, scope, elementRef, self.inputs,
|
||||
self.outputs, self.propertyOutputs,
|
||||
self.checkProperties, self.propertyMap);
|
||||
}
|
||||
],
|
||||
onChanges: function() { /* needs to be here for ng2 to properly detect it */ },
|
||||
|
@ -94,7 +94,7 @@ export class ExportedNg1Component {
|
|||
}
|
||||
}
|
||||
|
||||
static resolve(exportedComponents: {[name: string]: ExportedNg1Component},
|
||||
static resolve(exportedComponents: {[name: string]: UpgradeNg1ComponentAdapterBuilder},
|
||||
injector: angular.auto.IInjectorService) {
|
||||
for (var name in exportedComponents) {
|
||||
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;
|
||||
checkLastValues: any[] = [];
|
||||
|
|
@ -11,95 +11,100 @@ import {
|
|||
xit,
|
||||
} from 'angular2/testing_internal';
|
||||
|
||||
import {Component, View, Inject, EventEmitter} from 'angular2/angular2';
|
||||
import {createUpgradeModule, UpgradeModule} from 'upgrade/upgrade';
|
||||
import {Component, Inject, EventEmitter} from 'angular2/angular2';
|
||||
import {UpgradeAdapter} from 'upgrade/upgrade';
|
||||
|
||||
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 instantiate ng2 in ng1 template and project content',
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
var Ng2 = Component({selector: 'ng2'})
|
||||
.View({template: `{{ 'NG2' }}(<ng-content></ng-content>)`})
|
||||
var ng1Module = angular.module('ng1', []);
|
||||
var Ng2 = Component({selector: 'ng2', template: `{{ 'NG2' }}(<ng-content></ng-content>)`})
|
||||
.Class({constructor: function() {}});
|
||||
|
||||
var element = html("<div>{{ 'ng1[' }}<ng2>~{{ 'ng-content' }}~</ng2>{{ ']' }}</div>");
|
||||
|
||||
var upgradeModule: UpgradeModule = createUpgradeModule();
|
||||
upgradeModule.importNg2Component(Ng2);
|
||||
upgradeModule.bootstrap(element).ready(() => {
|
||||
expect(document.body.textContent).toEqual("ng1[NG2(~ng-content~)]");
|
||||
async.done();
|
||||
});
|
||||
var adapter: UpgradeAdapter = new UpgradeAdapter();
|
||||
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||
adapter.bootstrap(element, ['ng1'])
|
||||
.ready(() => {
|
||||
expect(document.body.textContent).toEqual("ng1[NG2(~ng-content~)]");
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should instantiate ng1 in ng2 template and project content',
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
var upgrMod: UpgradeModule = createUpgradeModule();
|
||||
var adapter: UpgradeAdapter = new UpgradeAdapter();
|
||||
var ng1Module = angular.module('ng1', []);
|
||||
|
||||
var Ng2 = Component({selector: 'ng2-1'})
|
||||
.View({
|
||||
template: `{{ 'ng2(' }}<ng1>{{'transclude'}}</ng1>{{ ')' }}`,
|
||||
directives: [upgrMod.exportAsNg2Component('ng1')]
|
||||
})
|
||||
.Class({constructor: function() {}});
|
||||
var Ng2 = Component({
|
||||
selector: 'ng2',
|
||||
template: `{{ 'ng2(' }}<ng1>{{'transclude'}}</ng1>{{ ')' }}`,
|
||||
directives: [adapter.upgradeNg1Component('ng1')]
|
||||
}).Class({constructor: function() {}});
|
||||
|
||||
upgrMod.ng1Module.directive('ng1', () => {
|
||||
ng1Module.directive('ng1', () => {
|
||||
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(() => {
|
||||
expect(document.body.textContent).toEqual("ng1(ng2(ng1(transclude)))");
|
||||
async.done();
|
||||
});
|
||||
adapter.bootstrap(element, ['ng1'])
|
||||
.ready(() => {
|
||||
expect(document.body.textContent).toEqual("ng1(ng2(ng1(transclude)))");
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
describe('scope/component change-detection', () => {
|
||||
it('should interleave scope and component expressions',
|
||||
inject([AsyncTestCompleter], (async) => {
|
||||
var ng1Module = angular.module('ng1', []);
|
||||
var log = [];
|
||||
var l = function(value) {
|
||||
log.push(value);
|
||||
return value + ';';
|
||||
};
|
||||
var upgrMod: UpgradeModule = createUpgradeModule();
|
||||
var adapter: UpgradeAdapter = new UpgradeAdapter();
|
||||
|
||||
upgrMod.ng1Module.directive('ng1a', () => { return {template: "{{ l('ng1a') }}"}; });
|
||||
upgrMod.ng1Module.directive('ng1b', () => { return {template: "{{ l('ng1b') }}"}; });
|
||||
upgrMod.ng1Module.run(($rootScope) => {
|
||||
ng1Module.directive('ng1a', () => { return {template: "{{ l('ng1a') }}"}; });
|
||||
ng1Module.directive('ng1b', () => { return {template: "{{ l('ng1b') }}"}; });
|
||||
ng1Module.run(($rootScope) => {
|
||||
$rootScope.l = l;
|
||||
$rootScope.reset = () => log.length = 0;
|
||||
});
|
||||
|
||||
upgrMod.importNg2Component(
|
||||
Component({selector: 'ng2'})
|
||||
.View({
|
||||
template: `{{l('2A')}}<ng1a></ng1a>{{l('2B')}}<ng1b></ng1b>{{l('2C')}}`,
|
||||
directives: [
|
||||
upgrMod.exportAsNg2Component('ng1a'),
|
||||
upgrMod.exportAsNg2Component('ng1b')
|
||||
]
|
||||
})
|
||||
.Class({constructor: function() { this.l = l; }}));
|
||||
var Ng2 =
|
||||
Component({
|
||||
selector: 'ng2',
|
||||
template: `{{l('2A')}}<ng1a></ng1a>{{l('2B')}}<ng1b></ng1b>{{l('2C')}}`,
|
||||
directives:
|
||||
[adapter.upgradeNg1Component('ng1a'), adapter.upgradeNg1Component('ng1b')]
|
||||
}).Class({constructor: function() { this.l = l; }});
|
||||
|
||||
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||
|
||||
var element = html("<div>{{reset(); l('1A');}}<ng2>{{l('1B')}}</ng2>{{l('1C')}}</div>");
|
||||
upgrMod.bootstrap(element).ready(() => {
|
||||
expect(document.body.textContent).toEqual("1A;2A;ng1a;2B;ng1b;2C;1C;");
|
||||
// https://github.com/angular/angular.js/issues/12983
|
||||
expect(log).toEqual(['1A', '1B', '1C', '2A', '2B', '2C', 'ng1a', 'ng1b']);
|
||||
async.done();
|
||||
});
|
||||
adapter.bootstrap(element, ['ng1'])
|
||||
.ready(() => {
|
||||
expect(document.body.textContent).toEqual("1A;2A;ng1a;2B;ng1b;2C;1C;");
|
||||
// https://github.com/angular/angular.js/issues/12983
|
||||
expect(log).toEqual(['1A', '1B', '1C', '2A', '2B', '2C', 'ng1a', 'ng1b']);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('binding from ng1 to ng2', () => {
|
||||
it('should bind properties, events', inject([AsyncTestCompleter], (async) => {
|
||||
var upgrMod: UpgradeModule = createUpgradeModule();
|
||||
upgrMod.ng1Module.run(($rootScope) => {
|
||||
var adapter: UpgradeAdapter = new UpgradeAdapter();
|
||||
var ng1Module = angular.module('ng1', []);
|
||||
|
||||
ng1Module.run(($rootScope) => {
|
||||
$rootScope.dataA = 'A';
|
||||
$rootScope.dataB = 'B';
|
||||
$rootScope.modelA = 'initModelA';
|
||||
|
@ -107,7 +112,7 @@ export function main() {
|
|||
$rootScope.eventA = '?';
|
||||
$rootScope.eventB = '?';
|
||||
});
|
||||
upgrMod.importNg2Component(
|
||||
var Ng2 =
|
||||
Component({
|
||||
selector: 'ng2',
|
||||
inputs: ['literal', 'interpolate', 'oneWayA', 'oneWayB', 'twoWayA', 'twoWayB'],
|
||||
|
@ -116,15 +121,12 @@ export function main() {
|
|||
'eventB',
|
||||
'twoWayAEmitter: twoWayAChange',
|
||||
'twoWayBEmitter: twoWayBChange'
|
||||
]
|
||||
],
|
||||
template: "ignore: {{ignore}}; " +
|
||||
"literal: {{literal}}; interpolate: {{interpolate}}; " +
|
||||
"oneWayA: {{oneWayA}}; oneWayB: {{oneWayB}}; " +
|
||||
"twoWayA: {{twoWayA}}; twoWayB: {{twoWayB}}; ({{onChangesCount}})"
|
||||
})
|
||||
.View({
|
||||
template:
|
||||
"ignore: {{ignore}}; " +
|
||||
"literal: {{literal}}; interpolate: {{interpolate}}; " +
|
||||
"oneWayA: {{oneWayA}}; oneWayB: {{oneWayB}}; " +
|
||||
"twoWayA: {{twoWayA}}; twoWayB: {{twoWayB}}; ({{onChangesCount}})"
|
||||
})
|
||||
.Class({
|
||||
constructor: function() {
|
||||
this.onChangesCount = 0;
|
||||
|
@ -185,7 +187,8 @@ export function main() {
|
|||
throw new Error('Called too many times! ' + JSON.stringify(changes));
|
||||
}
|
||||
}
|
||||
}));
|
||||
});
|
||||
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||
var element = html(`<div>
|
||||
<ng2 literal="Text" interpolate="Hello {{'world'}}"
|
||||
bind-one-way-a="dataA" [one-way-b]="dataB"
|
||||
|
@ -193,29 +196,32 @@ export function main() {
|
|||
on-event-a='eventA=$event' (event-b)="eventB=$event"></ng2>
|
||||
| modelA: {{modelA}}; modelB: {{modelB}}; eventA: {{eventA}}; eventB: {{eventB}};
|
||||
</div>`);
|
||||
upgrMod.bootstrap(element).ready(() => {
|
||||
expect(multiTrim(document.body.textContent))
|
||||
.toEqual(
|
||||
"ignore: -; " + "literal: Text; interpolate: Hello world; " +
|
||||
"oneWayA: A; oneWayB: B; twoWayA: initModelA; twoWayB: initModelB; (1) | " +
|
||||
"modelA: initModelA; modelB: initModelB; eventA: ?; eventB: ?;");
|
||||
setTimeout(() => {
|
||||
// we need to do setTimeout, because the EventEmitter uses setTimeout to schedule
|
||||
// events, and so without this we would not see the events processed.
|
||||
expect(multiTrim(document.body.textContent))
|
||||
.toEqual("ignore: -; " + "literal: Text; interpolate: Hello world; " +
|
||||
"oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (3) | " +
|
||||
"modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;");
|
||||
async.done();
|
||||
});
|
||||
});
|
||||
adapter.bootstrap(element, ['ng1'])
|
||||
.ready(() => {
|
||||
expect(multiTrim(document.body.textContent))
|
||||
.toEqual(
|
||||
"ignore: -; " + "literal: Text; interpolate: Hello world; " +
|
||||
"oneWayA: A; oneWayB: B; twoWayA: initModelA; twoWayB: initModelB; (1) | " +
|
||||
"modelA: initModelA; modelB: initModelB; eventA: ?; eventB: ?;");
|
||||
setTimeout(() => {
|
||||
// we need to do setTimeout, because the EventEmitter uses setTimeout to schedule
|
||||
// events, and so without this we would not see the events processed.
|
||||
expect(multiTrim(document.body.textContent))
|
||||
.toEqual("ignore: -; " + "literal: Text; interpolate: Hello world; " +
|
||||
"oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (3) | " +
|
||||
"modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;");
|
||||
async.done();
|
||||
});
|
||||
});
|
||||
|
||||
}));
|
||||
});
|
||||
|
||||
describe('binding from ng2 to ng1', () => {
|
||||
it('should bind properties, events', inject([AsyncTestCompleter], (async) => {
|
||||
var upgrMod = createUpgradeModule();
|
||||
var adapter = new UpgradeAdapter();
|
||||
var ng1Module = angular.module('ng1', []);
|
||||
|
||||
var ng1 = function() {
|
||||
return {
|
||||
template: 'Hello {{fullName}}; A: {{dataA}}; B: {{dataB}}; | ',
|
||||
|
@ -233,17 +239,17 @@ export function main() {
|
|||
}
|
||||
}
|
||||
};
|
||||
upgrMod.ng1Module.directive('ng1', ng1);
|
||||
var ng2 =
|
||||
Component({selector: 'ng2'})
|
||||
.View({
|
||||
template:
|
||||
'<ng1 full-name="{{last}}, {{first}}" [model-a]="first" [(model-b)]="last" ' +
|
||||
'(event)="event=$event"></ng1>' +
|
||||
'<ng1 full-name="{{\'TEST\'}}" model-a="First" model-b="Last"></ng1>' +
|
||||
'{{event}}-{{last}}, {{first}}',
|
||||
directives: [upgrMod.exportAsNg2Component('ng1')]
|
||||
})
|
||||
ng1Module.directive('ng1', ng1);
|
||||
var Ng2 =
|
||||
Component({
|
||||
selector: 'ng2',
|
||||
template:
|
||||
'<ng1 full-name="{{last}}, {{first}}" [model-a]="first" [(model-b)]="last" ' +
|
||||
'(event)="event=$event"></ng1>' +
|
||||
'<ng1 full-name="{{\'TEST\'}}" model-a="First" model-b="Last"></ng1>' +
|
||||
'{{event}}-{{last}}, {{first}}',
|
||||
directives: [adapter.upgradeNg1Component('ng1')]
|
||||
})
|
||||
.Class({
|
||||
constructor: function() {
|
||||
this.first = 'Victor';
|
||||
|
@ -251,18 +257,53 @@ export function main() {
|
|||
this.event = '?';
|
||||
}
|
||||
});
|
||||
upgrMod.importNg2Component(ng2);
|
||||
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||
var element = html(`<div><ng2></ng2></div>`);
|
||||
upgrMod.bootstrap(element).ready(() => {
|
||||
// we need to do setTimeout, because the EventEmitter uses setTimeout to schedule
|
||||
// events, and so without this we would not see the events processed.
|
||||
setTimeout(() => {
|
||||
expect(multiTrim(document.body.textContent))
|
||||
.toEqual(
|
||||
"Hello SAVKIN, Victor; A: VICTOR; B: SAVKIN; | Hello TEST; A: First; B: Last; | WORKS-SAVKIN, Victor");
|
||||
async.done();
|
||||
}, 0);
|
||||
adapter.bootstrap(element, ['ng1'])
|
||||
.ready(() => {
|
||||
// we need to do setTimeout, because the EventEmitter uses setTimeout to schedule
|
||||
// events, and so without this we would not see the events processed.
|
||||
setTimeout(() => {
|
||||
expect(multiTrim(document.body.textContent))
|
||||
.toEqual(
|
||||
"Hello SAVKIN, Victor; A: VICTOR; B: SAVKIN; | Hello TEST; A: First; B: Last; | WORKS-SAVKIN, Victor");
|
||||
async.done();
|
||||
}, 0);
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
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