parent
db6d289d82
commit
8427863bab
|
@ -1,5 +1,6 @@
|
||||||
import {isPresent} from 'angular2/src/core/facade/lang';
|
import {isPresent} from 'angular2/src/core/facade/lang';
|
||||||
import * as viewModule from './view';
|
import * as viewModule from './view';
|
||||||
|
import {ChangeDetectorRef} from '../change_detection/change_detector_ref';
|
||||||
import {RenderViewRef, RenderFragmentRef} from 'angular2/src/core/render/api';
|
import {RenderViewRef, RenderFragmentRef} from 'angular2/src/core/render/api';
|
||||||
|
|
||||||
// This is a workaround for privacy in Dart as we don't have library parts
|
// This is a workaround for privacy in Dart as we don't have library parts
|
||||||
|
@ -12,7 +13,7 @@ export function internalProtoView(protoViewRef: ProtoViewRef): viewModule.AppPro
|
||||||
return isPresent(protoViewRef) ? protoViewRef._protoView : null;
|
return isPresent(protoViewRef) ? protoViewRef._protoView : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HostViewRef {}
|
export interface HostViewRef { changeDetectorRef: ChangeDetectorRef; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A reference to an Angular View.
|
* A reference to an Angular View.
|
||||||
|
@ -66,6 +67,8 @@ export interface HostViewRef {}
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export class ViewRef implements HostViewRef {
|
export class ViewRef implements HostViewRef {
|
||||||
|
private _changeDetectorRef: ChangeDetectorRef = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
|
@ -81,6 +84,19 @@ export class ViewRef implements HostViewRef {
|
||||||
*/
|
*/
|
||||||
get renderFragment(): RenderFragmentRef { return this._view.renderFragment; }
|
get renderFragment(): RenderFragmentRef { return this._view.renderFragment; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return `ChangeDetectorRef`
|
||||||
|
*/
|
||||||
|
get changeDetectorRef(): ChangeDetectorRef {
|
||||||
|
if (this._changeDetectorRef === null) {
|
||||||
|
this._changeDetectorRef = this._view.changeDetector.ref;
|
||||||
|
}
|
||||||
|
return this._changeDetectorRef;
|
||||||
|
}
|
||||||
|
set changeDetectorRef(value: ChangeDetectorRef) {
|
||||||
|
throw "readonly"; // TODO: https://github.com/Microsoft/TypeScript/issues/12
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set local variable in a view.
|
* Set local variable in a view.
|
||||||
*
|
*
|
||||||
|
|
|
@ -1089,6 +1089,8 @@ var NG_API = [
|
||||||
'ViewQueryMetadata.token',
|
'ViewQueryMetadata.token',
|
||||||
'ViewQueryMetadata.varBindings',
|
'ViewQueryMetadata.varBindings',
|
||||||
'ViewRef',
|
'ViewRef',
|
||||||
|
'ViewRef.changeDetectorRef',
|
||||||
|
'ViewRef.changeDetectorRef=',
|
||||||
'ViewRef.render',
|
'ViewRef.render',
|
||||||
'ViewRef.renderFragment',
|
'ViewRef.renderFragment',
|
||||||
'ViewRef.setLocal()',
|
'ViewRef.setLocal()',
|
||||||
|
@ -1137,6 +1139,8 @@ var NG_API = [
|
||||||
'{DoCheck}',
|
'{DoCheck}',
|
||||||
'{Form}',
|
'{Form}',
|
||||||
'{HostViewRef}',
|
'{HostViewRef}',
|
||||||
|
'{HostViewRef}.changeDetectorRef',
|
||||||
|
'{HostViewRef}.changeDetectorRef=',
|
||||||
'{IterableDifferFactory}',
|
'{IterableDifferFactory}',
|
||||||
'{IterableDiffer}',
|
'{IterableDiffer}',
|
||||||
'{KeyValueDifferFactory}',
|
'{KeyValueDifferFactory}',
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
import {Type, ComponentMetadata, DirectiveResolver, DirectiveMetadata} from 'angular2/angular2';
|
||||||
|
import {stringify} from 'upgrade/src/util';
|
||||||
|
|
||||||
|
var COMPONENT_SELECTOR = /^[\w|-]*$/;
|
||||||
|
var SKEWER_CASE = /-(\w)/g;
|
||||||
|
var directiveResolver = new DirectiveResolver();
|
||||||
|
|
||||||
|
interface Reflect {
|
||||||
|
getOwnMetadata(name: string, type: Function): any;
|
||||||
|
defineMetadata(name: string, value: any, cls: Type): void;
|
||||||
|
}
|
||||||
|
var Reflect: Reflect = <Reflect>(<any>window).Reflect;
|
||||||
|
if (!(Reflect && (<any>Reflect)['getOwnMetadata'])) {
|
||||||
|
throw 'reflect-metadata shim is required when using class decorators';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getComponentSelector(type: Type): string {
|
||||||
|
var resolvedMetadata: DirectiveMetadata = directiveResolver.resolve(type);
|
||||||
|
var selector = resolvedMetadata.selector;
|
||||||
|
if (!selector.match(COMPONENT_SELECTOR)) {
|
||||||
|
throw new Error('Only selectors matching element names are supported, got: ' + selector);
|
||||||
|
}
|
||||||
|
return selector.replace(SKEWER_CASE, (all, letter: string) => letter.toUpperCase());
|
||||||
|
}
|
|
@ -0,0 +1,172 @@
|
||||||
|
///<reference path="../typings/angularjs/angular.d.ts"/>
|
||||||
|
|
||||||
|
import {
|
||||||
|
platform,
|
||||||
|
PlatformRef,
|
||||||
|
ApplicationRef,
|
||||||
|
ComponentRef,
|
||||||
|
bind,
|
||||||
|
Directive,
|
||||||
|
Component,
|
||||||
|
Inject,
|
||||||
|
View,
|
||||||
|
Type,
|
||||||
|
PlatformRef,
|
||||||
|
ApplicationRef,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
AppViewManager,
|
||||||
|
NgZone,
|
||||||
|
Injector,
|
||||||
|
Compiler,
|
||||||
|
ProtoViewRef,
|
||||||
|
ElementRef,
|
||||||
|
HostViewRef,
|
||||||
|
ViewRef
|
||||||
|
} from 'angular2/angular2';
|
||||||
|
import {applicationDomBindings} from 'angular2/src/core/application_common';
|
||||||
|
import {applicationCommonBindings} from "../../angular2/src/core/application_ref";
|
||||||
|
|
||||||
|
import {getComponentSelector} from './metadata';
|
||||||
|
import {onError} from './util';
|
||||||
|
export const INJECTOR = 'ng2.Injector';
|
||||||
|
export const APP_VIEW_MANAGER = 'ng2.AppViewManager';
|
||||||
|
export const NG2_COMPILER = 'ng2.Compiler';
|
||||||
|
export const NG2_ZONE = 'ng2.NgZone';
|
||||||
|
export const PROTO_VIEW_REF_MAP = 'ng2.ProtoViewRefMap';
|
||||||
|
|
||||||
|
const NG1_REQUIRE_INJECTOR_REF = '$' + INJECTOR + 'Controller';
|
||||||
|
const NG1_SCOPE = '$scope';
|
||||||
|
const NG1_COMPILE = '$compile';
|
||||||
|
const NG1_INJECTOR = '$injector';
|
||||||
|
const REQUIRE_INJECTOR = '^' + INJECTOR;
|
||||||
|
|
||||||
|
var moduleCount: number = 0;
|
||||||
|
const CAMEL_CASE = /([A-Z])/g;
|
||||||
|
|
||||||
|
export function createUpgradeModule(): UpgradeModule {
|
||||||
|
var prefix = `NG2_UPGRADE_m${moduleCount++}_`;
|
||||||
|
return new UpgradeModule(prefix, angular.module(prefix, []));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class UpgradeModule {
|
||||||
|
componentTypes: Array<Type> = [];
|
||||||
|
|
||||||
|
constructor(public idPrefix: string, public ng1Module: angular.IModule) {}
|
||||||
|
|
||||||
|
importNg2Component(type: Type): UpgradeModule {
|
||||||
|
this.componentTypes.push(type);
|
||||||
|
var selector: string = getComponentSelector(type);
|
||||||
|
var factory: Function = ng1ComponentDirective(selector, type, `${this.idPrefix}${selector}_c`);
|
||||||
|
this.ng1Module.directive(selector, <any[]>factory);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
exportAsNg2Component(name: string): Type {
|
||||||
|
return Directive({
|
||||||
|
selector: name.replace(CAMEL_CASE, (all, next: string) => '-' + next.toLowerCase())
|
||||||
|
})
|
||||||
|
.Class({
|
||||||
|
constructor: [
|
||||||
|
new Inject(NG1_COMPILE),
|
||||||
|
new Inject(NG1_SCOPE),
|
||||||
|
ElementRef,
|
||||||
|
function(compile: angular.ICompileService, scope: angular.IScope,
|
||||||
|
elementRef: ElementRef) { compile(elementRef.nativeElement)(scope); }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bootstrap(element: Element, modules?: any[],
|
||||||
|
config?: angular.IAngularBootstrapConfig): UpgradeRef {
|
||||||
|
var upgrade = new UpgradeRef();
|
||||||
|
var ng1Injector: angular.auto.IInjectorService = null;
|
||||||
|
var bindings = [
|
||||||
|
applicationCommonBindings(),
|
||||||
|
applicationDomBindings(),
|
||||||
|
bind(NG1_INJECTOR).toFactory(() => ng1Injector),
|
||||||
|
bind(NG1_COMPILE).toFactory(() => 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);
|
||||||
|
this.compileNg2Components(compiler).then((protoViewRefMap: ProtoViewRefMap) => {
|
||||||
|
ngZone.run(() => {
|
||||||
|
this.ng1Module.value(INJECTOR, injector)
|
||||||
|
.value(NG2_ZONE, ngZone)
|
||||||
|
.value(NG2_COMPILER, compiler)
|
||||||
|
.value(PROTO_VIEW_REF_MAP, protoViewRefMap)
|
||||||
|
.value(APP_VIEW_MANAGER, injector.get(AppViewManager))
|
||||||
|
.run([
|
||||||
|
'$injector',
|
||||||
|
'$rootScope',
|
||||||
|
(injector: angular.auto.IInjectorService, rootScope: angular.IRootScopeService) => {
|
||||||
|
ng1Injector = injector;
|
||||||
|
ngZone.overrideOnTurnDone(() => rootScope.$apply());
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
modules = modules ? [].concat(modules) : [];
|
||||||
|
modules.push(this.idPrefix);
|
||||||
|
angular.element(element).data(NG1_REQUIRE_INJECTOR_REF, injector);
|
||||||
|
angular.bootstrap(element, modules, config);
|
||||||
|
|
||||||
|
upgrade.readyFn && upgrade.readyFn();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return upgrade;
|
||||||
|
}
|
||||||
|
|
||||||
|
private compileNg2Components(compiler: Compiler): Promise<ProtoViewRefMap> {
|
||||||
|
var promises: Array<Promise<ProtoViewRef>> = [];
|
||||||
|
var types = this.componentTypes;
|
||||||
|
for (var i = 0; i < types.length; i++) {
|
||||||
|
promises.push(compiler.compileInHost(types[i]));
|
||||||
|
}
|
||||||
|
return Promise.all(promises).then((protoViews: Array<ProtoViewRef>) => {
|
||||||
|
var protoViewRefMap: ProtoViewRefMap = {};
|
||||||
|
var types = this.componentTypes;
|
||||||
|
for (var i = 0; i < protoViews.length; i++) {
|
||||||
|
protoViewRefMap[getComponentSelector(types[i])] = protoViews[i];
|
||||||
|
}
|
||||||
|
return protoViewRefMap;
|
||||||
|
}, onError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProtoViewRefMap {
|
||||||
|
[selector: string]: ProtoViewRef
|
||||||
|
}
|
||||||
|
|
||||||
|
function ng1ComponentDirective(selector: string, type: Type, idPrefix: string): Function {
|
||||||
|
directiveFactory.$inject = [PROTO_VIEW_REF_MAP, APP_VIEW_MANAGER];
|
||||||
|
function directiveFactory(protoViewRefMap: ProtoViewRefMap, viewManager: AppViewManager):
|
||||||
|
angular.IDirective {
|
||||||
|
var protoView: ProtoViewRef = protoViewRefMap[selector];
|
||||||
|
if (!protoView) throw new Error('Expecting ProtoViewRef for: ' + selector);
|
||||||
|
var idCount = 0;
|
||||||
|
return {
|
||||||
|
restrict: 'E',
|
||||||
|
require: REQUIRE_INJECTOR,
|
||||||
|
link: (scope: angular.IScope, element: angular.IAugmentedJQuery, attrs: angular.IAttributes,
|
||||||
|
parentInjector: any, transclude: angular.ITranscludeFunction): void => {
|
||||||
|
var id = element[0].id = idPrefix + (idCount++);
|
||||||
|
var childInjector = parentInjector.resolveAndCreateChild([bind(NG1_SCOPE).toValue(scope)]);
|
||||||
|
var hostViewRef = viewManager.createRootHostView(protoView, '#' + id, childInjector);
|
||||||
|
var changeDetector: ChangeDetectorRef = hostViewRef.changeDetectorRef;
|
||||||
|
scope.$watch(() => changeDetector.detectChanges());
|
||||||
|
element.bind('$remove', () => viewManager.destroyRootHostView(hostViewRef));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return directiveFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpgradeRef {
|
||||||
|
readyFn: Function;
|
||||||
|
|
||||||
|
ready(fn: Function) { this.readyFn = fn; }
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
export function stringify(obj: any): string {
|
||||||
|
if (typeof obj == 'function') return obj.name || obj.toString();
|
||||||
|
return '' + obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function onError(e: any) {
|
||||||
|
// TODO: (misko): We seem to not have a stack trace here!
|
||||||
|
console.log(e, e.stack);
|
||||||
|
throw e;
|
||||||
|
}
|
|
@ -11,8 +11,58 @@ import {
|
||||||
xit,
|
xit,
|
||||||
} from 'angular2/test_lib';
|
} from 'angular2/test_lib';
|
||||||
|
|
||||||
|
import {Component, View, Inject} from 'angular2/angular2';
|
||||||
|
import {createUpgradeModule, UpgradeModule, bootstrapHybrid} from 'upgrade/upgrade';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
describe('upgrade integration',
|
describe('upgrade: ng1 to ng2', () => {
|
||||||
() => { it('should run', () => { 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', inject([AsyncTestCompleter], (async) => {
|
||||||
|
var element = html("<div>{{ 'ng1-' }}<ng2>~~</ng2>{{ '-ng1' }}</div>");
|
||||||
|
|
||||||
|
var upgradeModule: UpgradeModule = createUpgradeModule();
|
||||||
|
upgradeModule.importNg2Component(SimpleComponent);
|
||||||
|
upgradeModule.bootstrap(element).ready(() => {
|
||||||
|
expect(document.body.textContent).toEqual("ng1-NG2-ng1");
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should instantiate ng1 in ng2 template', inject([AsyncTestCompleter], (async) => {
|
||||||
|
var element = html("<div>{{'ng1('}}<ng2-1></ng2-1>{{')'}}</div>");
|
||||||
|
|
||||||
|
ng1inNg2Module.bootstrap(element).ready(() => {
|
||||||
|
expect(document.body.textContent).toEqual("ng1(ng2(ng1 WORKS!))");
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'ng2'})
|
||||||
|
@View({template: `{{ 'NG2' }}`})
|
||||||
|
class SimpleComponent {
|
||||||
|
}
|
||||||
|
|
||||||
|
var ng1inNg2Module: UpgradeModule = createUpgradeModule();
|
||||||
|
|
||||||
|
@Component({selector: 'ng2-1'})
|
||||||
|
@View({
|
||||||
|
template: `{{ 'ng2(' }}<ng1></ng1>{{ ')' }}`,
|
||||||
|
directives: [ng1inNg2Module.exportAsNg2Component('ng1')]
|
||||||
|
})
|
||||||
|
class Ng2ContainsNg1 {
|
||||||
|
}
|
||||||
|
|
||||||
|
ng1inNg2Module.ng1Module.directive('ng1', () => { return {template: 'ng1 {{ "WORKS" }}!'}; });
|
||||||
|
ng1inNg2Module.importNg2Component(Ng2ContainsNg1);
|
||||||
|
|
||||||
|
|
||||||
|
function html(html: string): Element {
|
||||||
|
var body = document.body;
|
||||||
|
body.innerHTML = html;
|
||||||
|
if (body.childNodes.length == 1 && body.firstChild instanceof HTMLElement)
|
||||||
|
return <Element>body.firstChild;
|
||||||
|
return body;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
import {
|
||||||
|
AsyncTestCompleter,
|
||||||
|
beforeEach,
|
||||||
|
ddescribe,
|
||||||
|
describe,
|
||||||
|
expect,
|
||||||
|
iit,
|
||||||
|
inject,
|
||||||
|
it,
|
||||||
|
xdescribe,
|
||||||
|
xit,
|
||||||
|
} from 'angular2/test_lib';
|
||||||
|
|
||||||
|
import {Component, View} from 'angular2/angular2';
|
||||||
|
import {getComponentSelector} from 'upgrade/src/metadata';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe('upgrade metadata', () => {
|
||||||
|
it('should extract component selector',
|
||||||
|
() => { expect(getComponentSelector(ElementNameComponent)).toEqual('elementNameDashed'); });
|
||||||
|
|
||||||
|
|
||||||
|
describe('errors', () => {
|
||||||
|
it('should throw on missing selector', () => {
|
||||||
|
expect(() => getComponentSelector(AttributeNameComponent))
|
||||||
|
.toThrowErrorWith(
|
||||||
|
"Only selectors matching element names are supported, got: [attr-name]");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw on non element names', () => {
|
||||||
|
expect(() => getComponentSelector(NoAnnotationComponent))
|
||||||
|
.toThrowErrorWith("No Directive annotation found on NoAnnotationComponent");
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'element-name-dashed'})
|
||||||
|
@View({template: ``})
|
||||||
|
class ElementNameComponent {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: '[attr-name]'})
|
||||||
|
@View({template: ``})
|
||||||
|
class AttributeNameComponent {
|
||||||
|
}
|
||||||
|
|
||||||
|
class NoAnnotationComponent {}
|
|
@ -0,0 +1 @@
|
||||||
|
export {createUpgradeModule, UpgradeModule} from './src/upgrade_module';
|
Loading…
Reference in New Issue