diff --git a/modules/upgrade/src/angular.d.ts b/modules/upgrade/src/angular.d.ts
index a7099734c8..3aac7529e2 100644
--- a/modules/upgrade/src/angular.d.ts
+++ b/modules/upgrade/src/angular.d.ts
@@ -1,6 +1,7 @@
declare namespace angular {
function module(prefix: string, dependencies?: string[]);
interface IModule {
+ config(fn: any): IModule;
directive(selector: string, factory: any): IModule;
value(key: string, value: any): IModule;
run(a: any);
@@ -13,11 +14,19 @@ declare namespace angular {
$watch(expr: any, fn?: (a1?: any, a2?: any) => void);
$apply(): 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 IAngularBootstrapConfig {}
- interface IDirective {}
+ interface IDirective {
+ require?: string;
+ restrict?: string;
+ scope?: {[key: string]: string};
+ link?: Function;
+ }
interface IAttributes {
$observe(attr: string, fn: (v: string) => void);
}
diff --git a/modules/upgrade/src/constants.ts b/modules/upgrade/src/constants.ts
new file mode 100644
index 0000000000..27ed30ce06
--- /dev/null
+++ b/modules/upgrade/src/constants.ts
@@ -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;
diff --git a/modules/upgrade/src/ng1_facade.ts b/modules/upgrade/src/ng1_facade.ts
new file mode 100644
index 0000000000..d1a257846a
--- /dev/null
+++ b/modules/upgrade/src/ng1_facade.ts
@@ -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;
+ }
+}
diff --git a/modules/upgrade/src/ng2_facade.ts b/modules/upgrade/src/ng2_facade.ts
new file mode 100644
index 0000000000..63baa0dcdd
--- /dev/null
+++ b/modules/upgrade/src/ng2_facade.ts
@@ -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; }
+}
diff --git a/modules/upgrade/src/upgrade_module.ts b/modules/upgrade/src/upgrade_module.ts
index c7da2c8775..3f9abb433f 100644
--- a/modules/upgrade/src/upgrade_module.ts
+++ b/modules/upgrade/src/upgrade_module.ts
@@ -1,63 +1,55 @@
///
import {
- platform,
- ComponentRef,
bind,
- Directive,
- Component,
- Inject,
- View,
- Type,
- PlatformRef,
+ platform,
ApplicationRef,
- ChangeDetectorRef,
AppViewManager,
- NgZone,
- Injector,
Compiler,
+ Injector,
+ NgZone,
+ PlatformRef,
ProtoViewRef,
- ElementRef,
- HostViewRef,
- ViewRef,
- SimpleChange
+ Type
} from 'angular2/angular2';
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 {getComponentInfo, ComponentInfo} 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 NG1_PARSE = '$parse';
-const REQUIRE_INJECTOR = '^' + INJECTOR;
+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;
-const CAMEL_CASE = /([A-Z])/g;
-var INITIAL_VALUE = {};
export function createUpgradeModule(): UpgradeModule {
var prefix = `NG2_UPGRADE_m${moduleCount++}_`;
return new UpgradeModule(prefix, angular.module(prefix, []));
}
-
export class UpgradeModule {
- componentTypes: Array = [];
+ importedNg2Components: Type[] = [];
+ exportedNg1Components: {[name: string]: ExportedNg1Component} = {}
constructor(public idPrefix: string, public ng1Module: angular.IModule) {}
importNg2Component(type: Type): UpgradeModule {
- this.componentTypes.push(type);
+ this.importedNg2Components.push(type);
var info: ComponentInfo = getComponentInfo(type);
var factory: Function = ng1ComponentDirective(info, `${this.idPrefix}${info.selector}_c`);
this.ng1Module.directive(info.selector, factory);
@@ -65,18 +57,11 @@ export class UpgradeModule {
}
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); }
- ]
- });
+ if (this.exportedNg1Components.hasOwnProperty(name)) {
+ return this.exportedNg1Components[name].type;
+ } else {
+ return (this.exportedNg1Components[name] = new ExportedNg1Component(name)).type;
+ }
}
bootstrap(element: Element, modules?: any[],
@@ -96,42 +81,72 @@ export class UpgradeModule {
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());
- }
- ]);
+ 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);
-
- upgrade.readyFn && upgrade.readyFn();
- });
+ 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): Promise {
+ private compileNg2Components(compiler: Compiler,
+ protoViewRefMap: ProtoViewRefMap): Promise {
var promises: Array> = [];
- var types = this.componentTypes;
+ 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) => {
- var protoViewRefMap: ProtoViewRefMap = {};
- var types = this.componentTypes;
+ var types = this.importedNg2Components;
for (var i = 0; i < protoViews.length; i++) {
protoViewRefMap[getComponentInfo(types[i]).selector] = protoViews[i];
}
@@ -145,7 +160,7 @@ interface ProtoViewRefMap {
}
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,
parse: angular.IParseService): angular.IDirective {
var protoView: ProtoViewRef = protoViewRefMap[info.selector];
@@ -170,136 +185,6 @@ function ng1ComponentDirective(info: ComponentInfo, idPrefix: string): Function
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 {
readyFn: Function;
diff --git a/modules/upgrade/test/integration_spec.ts b/modules/upgrade/test/integration_spec.ts
index a0e6cb5a8a..d6c4d04d4c 100644
--- a/modules/upgrade/test/integration_spec.ts
+++ b/modules/upgrade/test/integration_spec.ts
@@ -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:
+ '' +
+ '' +
+ '{{event}}-{{last}}, {{first}}',
+ directives: [upgrMod.exportAsNg2Component('ng1')]
+ })
+ .Class({
+ constructor: function() {
+ this.first = 'Victor';
+ this.last = 'Savkin';
+ this.event = '?';
+ }
+ });
+ upgrMod.importNg2Component(ng2);
+ var element = html(`
`);
+ 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);
+ });
+ }));
+ });
+
});
}