parent
bb4fd2de94
commit
8e1d2fb325
|
@ -1,6 +1,7 @@
|
||||||
declare namespace angular {
|
declare namespace angular {
|
||||||
function module(prefix: string, dependencies?: string[]);
|
function module(prefix: string, dependencies?: string[]);
|
||||||
interface IModule {
|
interface IModule {
|
||||||
|
config(fn: any): IModule;
|
||||||
directive(selector: string, factory: any): IModule;
|
directive(selector: string, factory: any): IModule;
|
||||||
value(key: string, value: any): IModule;
|
value(key: string, value: any): IModule;
|
||||||
run(a: any);
|
run(a: any);
|
||||||
|
@ -13,11 +14,19 @@ declare namespace angular {
|
||||||
$watch(expr: any, fn?: (a1?: any, a2?: any) => void);
|
$watch(expr: any, fn?: (a1?: any, a2?: any) => void);
|
||||||
$apply(): any;
|
$apply(): any;
|
||||||
$apply(exp: string): any;
|
$apply(exp: string): any;
|
||||||
$apply(exp: (scope: IScope) => any): any;
|
$apply(exp: Function): any;
|
||||||
|
$$childTail: IScope;
|
||||||
|
$$childHead: IScope;
|
||||||
|
$$nextSibling: IScope;
|
||||||
}
|
}
|
||||||
interface IScope extends IRootScopeService {}
|
interface IScope extends IRootScopeService {}
|
||||||
interface IAngularBootstrapConfig {}
|
interface IAngularBootstrapConfig {}
|
||||||
interface IDirective {}
|
interface IDirective {
|
||||||
|
require?: string;
|
||||||
|
restrict?: string;
|
||||||
|
scope?: {[key: string]: string};
|
||||||
|
link?: Function;
|
||||||
|
}
|
||||||
interface IAttributes {
|
interface IAttributes {
|
||||||
$observe(attr: string, fn: (v: string) => void);
|
$observe(attr: string, fn: (v: string) => void);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
export const NG2_APP_VIEW_MANAGER = 'ng2.AppViewManager';
|
||||||
|
export const NG2_COMPILER = 'ng2.Compiler';
|
||||||
|
export const NG2_INJECTOR = 'ng2.Injector';
|
||||||
|
export const NG2_PROTO_VIEW_REF_MAP = 'ng2.ProtoViewRefMap';
|
||||||
|
export const NG2_ZONE = 'ng2.NgZone';
|
||||||
|
|
||||||
|
export const NG1_REQUIRE_INJECTOR_REF = '$' + NG2_INJECTOR + 'Controller';
|
||||||
|
export const NG1_SCOPE = '$scope';
|
||||||
|
export const NG1_ROOT_SCOPE = '$rootScope';
|
||||||
|
export const NG1_COMPILE = '$compile';
|
||||||
|
export const NG1_INJECTOR = '$injector';
|
||||||
|
export const NG1_PARSE = '$parse';
|
||||||
|
export const REQUIRE_INJECTOR = '^' + NG2_INJECTOR;
|
|
@ -0,0 +1,165 @@
|
||||||
|
import {
|
||||||
|
Directive,
|
||||||
|
DoCheck,
|
||||||
|
ElementRef,
|
||||||
|
EventEmitter,
|
||||||
|
Inject,
|
||||||
|
OnChanges,
|
||||||
|
SimpleChange,
|
||||||
|
Type
|
||||||
|
} from 'angular2/angular2';
|
||||||
|
import {NG1_COMPILE, NG1_SCOPE} from './constants';
|
||||||
|
|
||||||
|
const CAMEL_CASE = /([A-Z])/g;
|
||||||
|
const INITIAL_VALUE = {
|
||||||
|
__UNINITIALIZED__: true
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export class ExportedNg1Component {
|
||||||
|
type: Type;
|
||||||
|
inputs: string[] = [];
|
||||||
|
inputsRename: string[] = [];
|
||||||
|
outputs: string[] = [];
|
||||||
|
outputsRename: string[] = [];
|
||||||
|
propertyOutputs: string[] = [];
|
||||||
|
checkProperties: string[] = [];
|
||||||
|
propertyMap: {[name: string]: string} = {};
|
||||||
|
|
||||||
|
constructor(public name: string) {
|
||||||
|
var selector = name.replace(CAMEL_CASE, (all, next: string) => '-' + next.toLowerCase());
|
||||||
|
var self = this;
|
||||||
|
this.type =
|
||||||
|
Directive({selector: selector, inputs: this.inputsRename, outputs: this.outputsRename})
|
||||||
|
.Class({
|
||||||
|
constructor: [
|
||||||
|
new Inject(NG1_COMPILE),
|
||||||
|
new Inject(NG1_SCOPE),
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
],
|
||||||
|
onChanges: function() { /* needs to be here for ng2 to properly detect it */ },
|
||||||
|
doCheck: function() { /* needs to be here for ng2 to properly detect it */ }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
extractBindings(injector: angular.auto.IInjectorService) {
|
||||||
|
var directives: angular.IDirective[] = injector.get(this.name + 'Directive');
|
||||||
|
if (directives.length > 1) {
|
||||||
|
throw new Error('Only support single directive definition for: ' + this.name);
|
||||||
|
}
|
||||||
|
var directive = directives[0];
|
||||||
|
var scope = directive.scope;
|
||||||
|
if (typeof scope == 'object') {
|
||||||
|
for (var name in scope) {
|
||||||
|
if (scope.hasOwnProperty(name)) {
|
||||||
|
var localName = scope[name];
|
||||||
|
var type = localName.charAt(0);
|
||||||
|
localName = localName.substr(1) || name;
|
||||||
|
var outputName = 'output_' + name;
|
||||||
|
var outputNameRename = outputName + ': ' + name;
|
||||||
|
var inputName = 'input_' + name;
|
||||||
|
var inputNameRename = inputName + ': ' + name;
|
||||||
|
switch (type) {
|
||||||
|
case '=':
|
||||||
|
this.propertyOutputs.push(outputName);
|
||||||
|
this.checkProperties.push(localName);
|
||||||
|
this.outputs.push(outputName);
|
||||||
|
this.outputsRename.push(outputNameRename);
|
||||||
|
this.propertyMap[outputName] = localName;
|
||||||
|
// don't break; let it fall through to '@'
|
||||||
|
case '@':
|
||||||
|
this.inputs.push(inputName);
|
||||||
|
this.inputsRename.push(inputNameRename);
|
||||||
|
this.propertyMap[inputName] = localName;
|
||||||
|
break;
|
||||||
|
case '&':
|
||||||
|
this.outputs.push(outputName);
|
||||||
|
this.outputsRename.push(outputNameRename);
|
||||||
|
this.propertyMap[outputName] = localName;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
var json = JSON.stringify(scope);
|
||||||
|
throw new Error(
|
||||||
|
`Unexpected mapping '${type}' in '${json}' in '${this.name}' directive.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static resolve(exportedComponents: {[name: string]: ExportedNg1Component},
|
||||||
|
injector: angular.auto.IInjectorService) {
|
||||||
|
for (var name in exportedComponents) {
|
||||||
|
if (exportedComponents.hasOwnProperty(name)) {
|
||||||
|
var exportedComponent = exportedComponents[name];
|
||||||
|
exportedComponent.extractBindings(injector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Ng1ComponentFacade implements OnChanges, DoCheck {
|
||||||
|
componentScope: angular.IScope = null;
|
||||||
|
checkLastValues: any[] = [];
|
||||||
|
|
||||||
|
constructor(compile: angular.ICompileService, scope: angular.IScope, elementRef: ElementRef,
|
||||||
|
private inputs: string[], private outputs: string[], private propOuts: string[],
|
||||||
|
private checkProperties: string[], private propertyMap: {[key: string]: string}) {
|
||||||
|
var chailTail = scope.$$childTail; // remember where the next scope is inserted
|
||||||
|
compile(elementRef.nativeElement)(scope);
|
||||||
|
|
||||||
|
// If we are first scope take it, otherwise take the next one in list.
|
||||||
|
this.componentScope = chailTail ? chailTail.$$nextSibling : scope.$$childHead;
|
||||||
|
|
||||||
|
for (var i = 0; i < inputs.length; i++) {
|
||||||
|
this[inputs[i]] = null;
|
||||||
|
}
|
||||||
|
for (var j = 0; j < outputs.length; j++) {
|
||||||
|
var emitter = this[outputs[j]] = new EventEmitter();
|
||||||
|
this.setComponentProperty(outputs[j], ((emitter) => (value) => emitter.next(value))(emitter));
|
||||||
|
}
|
||||||
|
for (var k = 0; k < propOuts.length; k++) {
|
||||||
|
this[propOuts[k]] = new EventEmitter();
|
||||||
|
this.checkLastValues.push(INITIAL_VALUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onChanges(changes) {
|
||||||
|
for (var name in changes) {
|
||||||
|
if (changes.hasOwnProperty(name)) {
|
||||||
|
var change: SimpleChange = changes[name];
|
||||||
|
this.setComponentProperty(name, change.currentValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
doCheck() {
|
||||||
|
var count = 0;
|
||||||
|
var scope = this.componentScope;
|
||||||
|
var lastValues = this.checkLastValues;
|
||||||
|
var checkProperties = this.checkProperties;
|
||||||
|
for (var i = 0; i < checkProperties.length; i++) {
|
||||||
|
var value = scope[checkProperties[i]];
|
||||||
|
var last = lastValues[i];
|
||||||
|
if (value !== last) {
|
||||||
|
if (typeof value == 'number' && isNaN(value) && typeof last == 'number' && isNaN(last)) {
|
||||||
|
// ignore because NaN != NaN
|
||||||
|
} else {
|
||||||
|
var eventEmitter: EventEmitter = this[this.propOuts[i]];
|
||||||
|
eventEmitter.next(lastValues[i] = value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
setComponentProperty(name: string, value: any) {
|
||||||
|
this.componentScope[this.propertyMap[name]] = value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,144 @@
|
||||||
|
import {
|
||||||
|
bind,
|
||||||
|
AppViewManager,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
HostViewRef,
|
||||||
|
Injector,
|
||||||
|
ProtoViewRef,
|
||||||
|
SimpleChange
|
||||||
|
} from 'angular2/angular2';
|
||||||
|
import {NG1_SCOPE} from './constants';
|
||||||
|
import {ComponentInfo} from './metadata';
|
||||||
|
|
||||||
|
const INITIAL_VALUE = {
|
||||||
|
__UNINITIALIZED__: true
|
||||||
|
};
|
||||||
|
|
||||||
|
export class Ng2ComponentFacade {
|
||||||
|
component: any = null;
|
||||||
|
inputChangeCount: number = 0;
|
||||||
|
inputChanges: {[key: string]: SimpleChange} = null;
|
||||||
|
hostViewRef: HostViewRef = null;
|
||||||
|
changeDetector: ChangeDetectorRef = null;
|
||||||
|
componentScope: angular.IScope;
|
||||||
|
|
||||||
|
constructor(private id: string, private info: ComponentInfo,
|
||||||
|
private element: angular.IAugmentedJQuery, private attrs: angular.IAttributes,
|
||||||
|
private scope: angular.IScope, private parentInjector: Injector,
|
||||||
|
private parse: angular.IParseService, private viewManager: AppViewManager,
|
||||||
|
private protoView: ProtoViewRef) {
|
||||||
|
this.componentScope = scope.$new();
|
||||||
|
}
|
||||||
|
|
||||||
|
bootstrapNg2() {
|
||||||
|
var childInjector =
|
||||||
|
this.parentInjector.resolveAndCreateChild([bind(NG1_SCOPE).toValue(this.componentScope)]);
|
||||||
|
this.hostViewRef =
|
||||||
|
this.viewManager.createRootHostView(this.protoView, '#' + this.id, childInjector);
|
||||||
|
var hostElement = this.viewManager.getHostElement(this.hostViewRef);
|
||||||
|
this.changeDetector = this.hostViewRef.changeDetectorRef;
|
||||||
|
this.component = this.viewManager.getComponent(hostElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
setupInputs() {
|
||||||
|
var attrs = this.attrs;
|
||||||
|
var inputs = this.info.inputs;
|
||||||
|
for (var i = 0; i < inputs.length; i++) {
|
||||||
|
var input = inputs[i];
|
||||||
|
var expr = null;
|
||||||
|
if (attrs.hasOwnProperty(input.attr)) {
|
||||||
|
var observeFn = ((prop) => {
|
||||||
|
var prevValue = INITIAL_VALUE;
|
||||||
|
return (value) => {
|
||||||
|
if (this.inputChanges !== null) {
|
||||||
|
this.inputChangeCount++;
|
||||||
|
this.inputChanges[prop] =
|
||||||
|
new Ng1Change(value, prevValue === INITIAL_VALUE ? value : prevValue);
|
||||||
|
prevValue = value;
|
||||||
|
}
|
||||||
|
this.component[prop] = value;
|
||||||
|
}
|
||||||
|
})(input.prop);
|
||||||
|
attrs.$observe(input.attr, observeFn);
|
||||||
|
} else if (attrs.hasOwnProperty(input.bindAttr)) {
|
||||||
|
expr = attrs[input.bindAttr];
|
||||||
|
} else if (attrs.hasOwnProperty(input.bracketAttr)) {
|
||||||
|
expr = attrs[input.bracketAttr];
|
||||||
|
} else if (attrs.hasOwnProperty(input.bindonAttr)) {
|
||||||
|
expr = attrs[input.bindonAttr];
|
||||||
|
} else if (attrs.hasOwnProperty(input.bracketParenAttr)) {
|
||||||
|
expr = attrs[input.bracketParenAttr];
|
||||||
|
}
|
||||||
|
if (expr != null) {
|
||||||
|
var watchFn = ((prop) => (value, prevValue) => {
|
||||||
|
if (this.inputChanges != null) {
|
||||||
|
this.inputChangeCount++;
|
||||||
|
this.inputChanges[prop] = new Ng1Change(prevValue, value);
|
||||||
|
}
|
||||||
|
this.component[prop] = value;
|
||||||
|
})(input.prop);
|
||||||
|
this.componentScope.$watch(expr, watchFn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var prototype = this.info.type.prototype;
|
||||||
|
if (prototype && prototype.onChanges) {
|
||||||
|
// Detect: OnChanges interface
|
||||||
|
this.inputChanges = {};
|
||||||
|
this.componentScope.$watch(() => this.inputChangeCount, () => {
|
||||||
|
var inputChanges = this.inputChanges;
|
||||||
|
this.inputChanges = {};
|
||||||
|
this.component.onChanges(inputChanges);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.componentScope.$watch(() => this.changeDetector.detectChanges());
|
||||||
|
}
|
||||||
|
|
||||||
|
setupOutputs() {
|
||||||
|
var attrs = this.attrs;
|
||||||
|
var outputs = this.info.outputs;
|
||||||
|
for (var j = 0; j < outputs.length; j++) {
|
||||||
|
var output = outputs[j];
|
||||||
|
var expr = null;
|
||||||
|
var assignExpr = false;
|
||||||
|
if (attrs.hasOwnProperty(output.onAttr)) {
|
||||||
|
expr = attrs[output.onAttr];
|
||||||
|
} else if (attrs.hasOwnProperty(output.parenAttr)) {
|
||||||
|
expr = attrs[output.parenAttr];
|
||||||
|
} else if (attrs.hasOwnProperty(output.bindonAttr)) {
|
||||||
|
expr = attrs[output.bindonAttr];
|
||||||
|
assignExpr = true;
|
||||||
|
} else if (attrs.hasOwnProperty(output.bracketParenAttr)) {
|
||||||
|
expr = attrs[output.bracketParenAttr];
|
||||||
|
assignExpr = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expr != null && assignExpr != null) {
|
||||||
|
var getter = this.parse(expr);
|
||||||
|
var setter = getter.assign;
|
||||||
|
if (assignExpr && !setter) {
|
||||||
|
throw new Error(`Expression '${expr}' is not assignable!`);
|
||||||
|
}
|
||||||
|
var emitter = this.component[output.prop];
|
||||||
|
if (emitter) {
|
||||||
|
emitter.observer({
|
||||||
|
next: assignExpr ? ((setter) => (value) => setter(this.scope, value))(setter) :
|
||||||
|
((getter) => (value) => getter(this.scope, {$event: value}))(getter)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error(`Missing emitter '${output.prop}' on component '${this.info.selector}'!`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerCleanup() {
|
||||||
|
this.element.bind('$remove', () => this.viewManager.destroyRootHostView(this.hostViewRef));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Ng1Change implements SimpleChange {
|
||||||
|
constructor(public previousValue: any, public currentValue: any) {}
|
||||||
|
|
||||||
|
isFirstChange(): boolean { return this.previousValue === this.currentValue; }
|
||||||
|
}
|
|
@ -1,63 +1,55 @@
|
||||||
///<reference path="./angular.d.ts"/>
|
///<reference path="./angular.d.ts"/>
|
||||||
|
|
||||||
import {
|
import {
|
||||||
platform,
|
|
||||||
ComponentRef,
|
|
||||||
bind,
|
bind,
|
||||||
Directive,
|
platform,
|
||||||
Component,
|
|
||||||
Inject,
|
|
||||||
View,
|
|
||||||
Type,
|
|
||||||
PlatformRef,
|
|
||||||
ApplicationRef,
|
ApplicationRef,
|
||||||
ChangeDetectorRef,
|
|
||||||
AppViewManager,
|
AppViewManager,
|
||||||
NgZone,
|
|
||||||
Injector,
|
|
||||||
Compiler,
|
Compiler,
|
||||||
|
Injector,
|
||||||
|
NgZone,
|
||||||
|
PlatformRef,
|
||||||
ProtoViewRef,
|
ProtoViewRef,
|
||||||
ElementRef,
|
Type
|
||||||
HostViewRef,
|
|
||||||
ViewRef,
|
|
||||||
SimpleChange
|
|
||||||
} from 'angular2/angular2';
|
} from 'angular2/angular2';
|
||||||
import {applicationDomBindings} from 'angular2/src/core/application_common';
|
import {applicationDomBindings} from 'angular2/src/core/application_common';
|
||||||
import {applicationCommonBindings} from '../../angular2/src/core/application_ref';
|
import {applicationCommonBindings} from 'angular2/src/core/application_ref';
|
||||||
import {compilerBindings} from 'angular2/src/core/compiler/compiler';
|
import {compilerBindings} from 'angular2/src/core/compiler/compiler';
|
||||||
|
|
||||||
import {getComponentInfo, ComponentInfo} from './metadata';
|
import {getComponentInfo, ComponentInfo} from './metadata';
|
||||||
import {onError} from './util';
|
import {onError} from './util';
|
||||||
export const INJECTOR = 'ng2.Injector';
|
import {
|
||||||
export const APP_VIEW_MANAGER = 'ng2.AppViewManager';
|
NG1_COMPILE,
|
||||||
export const NG2_COMPILER = 'ng2.Compiler';
|
NG1_INJECTOR,
|
||||||
export const NG2_ZONE = 'ng2.NgZone';
|
NG1_PARSE,
|
||||||
export const PROTO_VIEW_REF_MAP = 'ng2.ProtoViewRefMap';
|
NG1_ROOT_SCOPE,
|
||||||
|
NG1_REQUIRE_INJECTOR_REF,
|
||||||
const NG1_REQUIRE_INJECTOR_REF = '$' + INJECTOR + 'Controller';
|
NG1_SCOPE,
|
||||||
const NG1_SCOPE = '$scope';
|
NG2_APP_VIEW_MANAGER,
|
||||||
const NG1_COMPILE = '$compile';
|
NG2_COMPILER,
|
||||||
const NG1_INJECTOR = '$injector';
|
NG2_INJECTOR,
|
||||||
const NG1_PARSE = '$parse';
|
NG2_PROTO_VIEW_REF_MAP,
|
||||||
const REQUIRE_INJECTOR = '^' + INJECTOR;
|
NG2_ZONE,
|
||||||
|
REQUIRE_INJECTOR
|
||||||
|
} from './constants';
|
||||||
|
import {Ng2ComponentFacade} from './ng2_facade';
|
||||||
|
import {ExportedNg1Component} from './ng1_facade';
|
||||||
|
|
||||||
var moduleCount: number = 0;
|
var moduleCount: number = 0;
|
||||||
const CAMEL_CASE = /([A-Z])/g;
|
|
||||||
var INITIAL_VALUE = {};
|
|
||||||
|
|
||||||
export function createUpgradeModule(): UpgradeModule {
|
export function createUpgradeModule(): UpgradeModule {
|
||||||
var prefix = `NG2_UPGRADE_m${moduleCount++}_`;
|
var prefix = `NG2_UPGRADE_m${moduleCount++}_`;
|
||||||
return new UpgradeModule(prefix, angular.module(prefix, []));
|
return new UpgradeModule(prefix, angular.module(prefix, []));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class UpgradeModule {
|
export class UpgradeModule {
|
||||||
componentTypes: Array<Type> = [];
|
importedNg2Components: Type[] = [];
|
||||||
|
exportedNg1Components: {[name: string]: ExportedNg1Component} = {}
|
||||||
|
|
||||||
constructor(public idPrefix: string, public ng1Module: angular.IModule) {}
|
constructor(public idPrefix: string, public ng1Module: angular.IModule) {}
|
||||||
|
|
||||||
importNg2Component(type: Type): UpgradeModule {
|
importNg2Component(type: Type): UpgradeModule {
|
||||||
this.componentTypes.push(type);
|
this.importedNg2Components.push(type);
|
||||||
var info: ComponentInfo = getComponentInfo(type);
|
var info: ComponentInfo = getComponentInfo(type);
|
||||||
var factory: Function = ng1ComponentDirective(info, `${this.idPrefix}${info.selector}_c`);
|
var factory: Function = ng1ComponentDirective(info, `${this.idPrefix}${info.selector}_c`);
|
||||||
this.ng1Module.directive(info.selector, <any>factory);
|
this.ng1Module.directive(info.selector, <any>factory);
|
||||||
|
@ -65,18 +57,11 @@ export class UpgradeModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
exportAsNg2Component(name: string): Type {
|
exportAsNg2Component(name: string): Type {
|
||||||
return Directive({
|
if (this.exportedNg1Components.hasOwnProperty(name)) {
|
||||||
selector: name.replace(CAMEL_CASE, (all, next: string) => '-' + next.toLowerCase())
|
return this.exportedNg1Components[name].type;
|
||||||
})
|
} else {
|
||||||
.Class({
|
return (this.exportedNg1Components[name] = new ExportedNg1Component(name)).type;
|
||||||
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[],
|
bootstrap(element: Element, modules?: any[],
|
||||||
|
@ -96,19 +81,42 @@ export class UpgradeModule {
|
||||||
var injector: Injector = applicationRef.injector;
|
var injector: Injector = applicationRef.injector;
|
||||||
var ngZone: NgZone = injector.get(NgZone);
|
var ngZone: NgZone = injector.get(NgZone);
|
||||||
var compiler: Compiler = injector.get(Compiler);
|
var compiler: Compiler = injector.get(Compiler);
|
||||||
this.compileNg2Components(compiler).then((protoViewRefMap: ProtoViewRefMap) => {
|
var delayApplyExps: Function[] = [];
|
||||||
|
var original$applyFn: Function;
|
||||||
|
var rootScopePrototype: any;
|
||||||
|
var rootScope: angular.IRootScopeService;
|
||||||
|
var protoViewRefMap: ProtoViewRefMap = {};
|
||||||
ngZone.run(() => {
|
ngZone.run(() => {
|
||||||
this.ng1Module.value(INJECTOR, injector)
|
this.ng1Module.value(NG2_INJECTOR, injector)
|
||||||
.value(NG2_ZONE, ngZone)
|
.value(NG2_ZONE, ngZone)
|
||||||
.value(NG2_COMPILER, compiler)
|
.value(NG2_COMPILER, compiler)
|
||||||
.value(PROTO_VIEW_REF_MAP, protoViewRefMap)
|
.value(NG2_PROTO_VIEW_REF_MAP, protoViewRefMap)
|
||||||
.value(APP_VIEW_MANAGER, injector.get(AppViewManager))
|
.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([
|
.run([
|
||||||
'$injector',
|
'$injector',
|
||||||
'$rootScope',
|
'$rootScope',
|
||||||
(injector: angular.auto.IInjectorService, rootScope: angular.IRootScopeService) => {
|
(injector: angular.auto.IInjectorService, rootScope: angular.IRootScopeService) => {
|
||||||
ng1Injector = injector;
|
ng1Injector = injector;
|
||||||
ngZone.overrideOnTurnDone(() => rootScope.$apply());
|
ngZone.overrideOnTurnDone(() => rootScope.$apply());
|
||||||
|
ExportedNg1Component.resolve(this.exportedNg1Components, injector);
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -116,22 +124,29 @@ export class UpgradeModule {
|
||||||
modules.push(this.idPrefix);
|
modules.push(this.idPrefix);
|
||||||
angular.element(element).data(NG1_REQUIRE_INJECTOR_REF, injector);
|
angular.element(element).data(NG1_REQUIRE_INJECTOR_REF, injector);
|
||||||
angular.bootstrap(element, modules, config);
|
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();
|
upgrade.readyFn && upgrade.readyFn();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return upgrade;
|
return upgrade;
|
||||||
}
|
}
|
||||||
|
|
||||||
private compileNg2Components(compiler: Compiler): Promise<ProtoViewRefMap> {
|
private compileNg2Components(compiler: Compiler,
|
||||||
|
protoViewRefMap: ProtoViewRefMap): Promise<ProtoViewRefMap> {
|
||||||
var promises: Array<Promise<ProtoViewRef>> = [];
|
var promises: Array<Promise<ProtoViewRef>> = [];
|
||||||
var types = this.componentTypes;
|
var types = this.importedNg2Components;
|
||||||
for (var i = 0; i < types.length; i++) {
|
for (var i = 0; i < types.length; i++) {
|
||||||
promises.push(compiler.compileInHost(types[i]));
|
promises.push(compiler.compileInHost(types[i]));
|
||||||
}
|
}
|
||||||
return Promise.all(promises).then((protoViews: Array<ProtoViewRef>) => {
|
return Promise.all(promises).then((protoViews: Array<ProtoViewRef>) => {
|
||||||
var protoViewRefMap: ProtoViewRefMap = {};
|
var types = this.importedNg2Components;
|
||||||
var types = this.componentTypes;
|
|
||||||
for (var i = 0; i < protoViews.length; i++) {
|
for (var i = 0; i < protoViews.length; i++) {
|
||||||
protoViewRefMap[getComponentInfo(types[i]).selector] = protoViews[i];
|
protoViewRefMap[getComponentInfo(types[i]).selector] = protoViews[i];
|
||||||
}
|
}
|
||||||
|
@ -145,7 +160,7 @@ interface ProtoViewRefMap {
|
||||||
}
|
}
|
||||||
|
|
||||||
function ng1ComponentDirective(info: ComponentInfo, idPrefix: string): Function {
|
function ng1ComponentDirective(info: ComponentInfo, idPrefix: string): Function {
|
||||||
directiveFactory.$inject = [PROTO_VIEW_REF_MAP, APP_VIEW_MANAGER, NG1_PARSE];
|
directiveFactory.$inject = [NG2_PROTO_VIEW_REF_MAP, NG2_APP_VIEW_MANAGER, NG1_PARSE];
|
||||||
function directiveFactory(protoViewRefMap: ProtoViewRefMap, viewManager: AppViewManager,
|
function directiveFactory(protoViewRefMap: ProtoViewRefMap, viewManager: AppViewManager,
|
||||||
parse: angular.IParseService): angular.IDirective {
|
parse: angular.IParseService): angular.IDirective {
|
||||||
var protoView: ProtoViewRef = protoViewRefMap[info.selector];
|
var protoView: ProtoViewRef = protoViewRefMap[info.selector];
|
||||||
|
@ -170,136 +185,6 @@ function ng1ComponentDirective(info: ComponentInfo, idPrefix: string): Function
|
||||||
return directiveFactory;
|
return directiveFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Ng2ComponentFacade {
|
|
||||||
component: any = null;
|
|
||||||
inputChangeCount: number = 0;
|
|
||||||
inputChanges: {[key: string]: SimpleChange} = null;
|
|
||||||
hostViewRef: HostViewRef = null;
|
|
||||||
changeDetector: ChangeDetectorRef = null;
|
|
||||||
componentScope: angular.IScope;
|
|
||||||
|
|
||||||
constructor(private id: string, private info: ComponentInfo,
|
|
||||||
private element: angular.IAugmentedJQuery, private attrs: angular.IAttributes,
|
|
||||||
private scope: angular.IScope, private parentInjector: Injector,
|
|
||||||
private parse: angular.IParseService, private viewManager: AppViewManager,
|
|
||||||
private protoView: ProtoViewRef) {
|
|
||||||
this.componentScope = scope.$new();
|
|
||||||
}
|
|
||||||
|
|
||||||
bootstrapNg2() {
|
|
||||||
var childInjector =
|
|
||||||
this.parentInjector.resolveAndCreateChild([bind(NG1_SCOPE).toValue(this.componentScope)]);
|
|
||||||
this.hostViewRef =
|
|
||||||
this.viewManager.createRootHostView(this.protoView, '#' + this.id, childInjector);
|
|
||||||
var hostElement = this.viewManager.getHostElement(this.hostViewRef);
|
|
||||||
this.changeDetector = this.hostViewRef.changeDetectorRef;
|
|
||||||
this.component = this.viewManager.getComponent(hostElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
setupInputs() {
|
|
||||||
var attrs = this.attrs;
|
|
||||||
var inputs = this.info.inputs;
|
|
||||||
for (var i = 0; i < inputs.length; i++) {
|
|
||||||
var input = inputs[i];
|
|
||||||
var expr = null;
|
|
||||||
if (attrs.hasOwnProperty(input.attr)) {
|
|
||||||
var observeFn = ((prop) => {
|
|
||||||
var prevValue = INITIAL_VALUE;
|
|
||||||
return (value) => {
|
|
||||||
if (this.inputChanges !== null) {
|
|
||||||
this.inputChangeCount++;
|
|
||||||
this.inputChanges[prop] =
|
|
||||||
new Ng1Change(value, prevValue === INITIAL_VALUE ? value : prevValue);
|
|
||||||
prevValue = value;
|
|
||||||
}
|
|
||||||
this.component[prop] = value;
|
|
||||||
};
|
|
||||||
})(input.prop);
|
|
||||||
attrs.$observe(input.attr, observeFn);
|
|
||||||
} else if (attrs.hasOwnProperty(input.bindAttr)) {
|
|
||||||
expr = attrs[input.bindAttr];
|
|
||||||
} else if (attrs.hasOwnProperty(input.bracketAttr)) {
|
|
||||||
expr = attrs[input.bracketAttr];
|
|
||||||
} else if (attrs.hasOwnProperty(input.bindonAttr)) {
|
|
||||||
expr = attrs[input.bindonAttr];
|
|
||||||
} else if (attrs.hasOwnProperty(input.bracketParenAttr)) {
|
|
||||||
expr = attrs[input.bracketParenAttr];
|
|
||||||
}
|
|
||||||
if (expr != null) {
|
|
||||||
var watchFn = ((prop) => (value, prevValue) => {
|
|
||||||
if (this.inputChanges != null) {
|
|
||||||
this.inputChangeCount++;
|
|
||||||
this.inputChanges[prop] = new Ng1Change(prevValue, value);
|
|
||||||
}
|
|
||||||
this.component[prop] = value;
|
|
||||||
})(input.prop);
|
|
||||||
this.componentScope.$watch(expr, watchFn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var prototype = this.info.type.prototype;
|
|
||||||
if (prototype && prototype.onChanges) {
|
|
||||||
// Detect: OnChanges interface
|
|
||||||
this.inputChanges = {};
|
|
||||||
this.componentScope.$watch(() => this.inputChangeCount, () => {
|
|
||||||
var inputChanges = this.inputChanges;
|
|
||||||
this.inputChanges = {};
|
|
||||||
this.component.onChanges(inputChanges);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.componentScope.$watch(() => this.changeDetector.detectChanges());
|
|
||||||
}
|
|
||||||
|
|
||||||
setupOutputs() {
|
|
||||||
var attrs = this.attrs;
|
|
||||||
var outputs = this.info.outputs;
|
|
||||||
for (var j = 0; j < outputs.length; j++) {
|
|
||||||
var output = outputs[j];
|
|
||||||
var expr = null;
|
|
||||||
var assignExpr = false;
|
|
||||||
if (attrs.hasOwnProperty(output.onAttr)) {
|
|
||||||
expr = attrs[output.onAttr];
|
|
||||||
} else if (attrs.hasOwnProperty(output.parenAttr)) {
|
|
||||||
expr = attrs[output.parenAttr];
|
|
||||||
} else if (attrs.hasOwnProperty(output.bindonAttr)) {
|
|
||||||
expr = attrs[output.bindonAttr];
|
|
||||||
assignExpr = true;
|
|
||||||
} else if (attrs.hasOwnProperty(output.bracketParenAttr)) {
|
|
||||||
expr = attrs[output.bracketParenAttr];
|
|
||||||
assignExpr = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expr != null && assignExpr != null) {
|
|
||||||
var getter = this.parse(expr);
|
|
||||||
var setter = getter.assign;
|
|
||||||
if (assignExpr && !setter) {
|
|
||||||
throw new Error(`Expression '${expr}' is not assignable!`);
|
|
||||||
}
|
|
||||||
var emitter = this.component[output.prop];
|
|
||||||
if (emitter) {
|
|
||||||
emitter.observer({
|
|
||||||
next: assignExpr ? ((setter) => (value) => setter(this.scope, value))(setter) :
|
|
||||||
((getter) => (value) => getter(this.scope, {$event: value}))(getter)
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw new Error(`Missing emitter '${output.prop}' on component '${this.info.selector}'!`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
registerCleanup() {
|
|
||||||
this.element.bind('$remove', () => this.viewManager.destroyRootHostView(this.hostViewRef));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Ng1Change implements SimpleChange {
|
|
||||||
constructor(public previousValue: any, public currentValue: any) {}
|
|
||||||
|
|
||||||
isFirstChange(): boolean { return this.previousValue === this.currentValue; }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class UpgradeRef {
|
export class UpgradeRef {
|
||||||
readyFn: Function;
|
readyFn: Function;
|
||||||
|
|
||||||
|
|
|
@ -205,6 +205,60 @@ export function main() {
|
||||||
|
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('binding from ng2 to ng1', () => {
|
||||||
|
it('should bind properties, events', inject([AsyncTestCompleter], (async) => {
|
||||||
|
var upgrMod = createUpgradeModule();
|
||||||
|
var ng1 = function() {
|
||||||
|
return {
|
||||||
|
template: 'Hello {{fullName}}; A: {{dataA}}; B: {{dataB}}; | ',
|
||||||
|
scope: {fullName: '@', modelA: '=dataA', modelB: '=dataB', event: '&'},
|
||||||
|
link: function(scope) {
|
||||||
|
scope.$watch('dataB', (v) => {
|
||||||
|
if (v == 'Savkin') {
|
||||||
|
scope.dataB = 'SAVKIN';
|
||||||
|
scope.event('WORKS');
|
||||||
|
|
||||||
|
// Should not update becaus [model-a] is uni directional
|
||||||
|
scope.dataA = 'VICTOR';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
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')]
|
||||||
|
})
|
||||||
|
.Class({
|
||||||
|
constructor: function() {
|
||||||
|
this.first = 'Victor';
|
||||||
|
this.last = 'Savkin';
|
||||||
|
this.event = '?';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
upgrMod.importNg2Component(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);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue