parent
db6d289d82
commit
8427863bab
|
@ -1,5 +1,6 @@
|
|||
import {isPresent} from 'angular2/src/core/facade/lang';
|
||||
import * as viewModule from './view';
|
||||
import {ChangeDetectorRef} from '../change_detection/change_detector_ref';
|
||||
import {RenderViewRef, RenderFragmentRef} from 'angular2/src/core/render/api';
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
export interface HostViewRef {}
|
||||
export interface HostViewRef { changeDetectorRef: ChangeDetectorRef; }
|
||||
|
||||
/**
|
||||
* A reference to an Angular View.
|
||||
|
@ -66,6 +67,8 @@ export interface HostViewRef {}
|
|||
* ```
|
||||
*/
|
||||
export class ViewRef implements HostViewRef {
|
||||
private _changeDetectorRef: ChangeDetectorRef = null;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
|
@ -81,6 +84,19 @@ export class ViewRef implements HostViewRef {
|
|||
*/
|
||||
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.
|
||||
*
|
||||
|
|
|
@ -1089,6 +1089,8 @@ var NG_API = [
|
|||
'ViewQueryMetadata.token',
|
||||
'ViewQueryMetadata.varBindings',
|
||||
'ViewRef',
|
||||
'ViewRef.changeDetectorRef',
|
||||
'ViewRef.changeDetectorRef=',
|
||||
'ViewRef.render',
|
||||
'ViewRef.renderFragment',
|
||||
'ViewRef.setLocal()',
|
||||
|
@ -1137,6 +1139,8 @@ var NG_API = [
|
|||
'{DoCheck}',
|
||||
'{Form}',
|
||||
'{HostViewRef}',
|
||||
'{HostViewRef}.changeDetectorRef',
|
||||
'{HostViewRef}.changeDetectorRef=',
|
||||
'{IterableDifferFactory}',
|
||||
'{IterableDiffer}',
|
||||
'{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,
|
||||
} from 'angular2/test_lib';
|
||||
|
||||
import {Component, View, Inject} from 'angular2/angular2';
|
||||
import {createUpgradeModule, UpgradeModule, bootstrapHybrid} from 'upgrade/upgrade';
|
||||
|
||||
export function main() {
|
||||
describe('upgrade integration',
|
||||
() => { it('should run', () => { expect(angular.version.major).toBe(1); }); });
|
||||
describe('upgrade: ng1 to ng2', () => {
|
||||
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