X Tutup
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions modules/upgrade/src/angular.d.ts
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -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);
}
Expand Down
13 changes: 13 additions & 0 deletions modules/upgrade/src/constants.ts
Original file line number Diff line number Diff line change
@@ -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;
165 changes: 165 additions & 0 deletions modules/upgrade/src/ng1_facade.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
144 changes: 144 additions & 0 deletions modules/upgrade/src/ng2_facade.ts
Original file line number Diff line number Diff line change
@@ -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; }
}
Loading
X Tutup