feat(ngUpgrade): faster ng2->ng1 adapter by only compiling ng1 once
The adapter only compiles ng1 template. This means that we need to reimplement / emulate all of the ng1’s API on the HOST element. interface IDirective { compile?: IDirectiveCompileFn; // NOT SUPPORTED controller?: any; // IMPLEMENTED controllerAs?: string; // IMPLEMENTED bindToController?: boolean|Object; // IMPLEMENTED link?: IDirectiveLinkFn | IDirectivePrePost; // IMPLEMENTED (pre-link only) name?: string; // N/A priority?: number; // NOT SUPPORTED replace?: boolean; // NOT SUPPORTED require?: any; // IMPLEMENTED restrict?: string; // WORKING scope?: any; // IMPLEMENTED template?: any; // IMPLEMENTED templateUrl?: any; // IMPLEMENTED terminal?: boolean; // NOT SUPPORTED transclude?: any; // IMPLEMENTED }
This commit is contained in:
parent
059e8faae2
commit
053b7a50e1
|
@ -7,7 +7,7 @@ declare namespace angular {
|
||||||
run(a: any);
|
run(a: any);
|
||||||
}
|
}
|
||||||
interface ICompileService {
|
interface ICompileService {
|
||||||
(element: Element, transclude?: Function): ILinkFn;
|
(element: Element | NodeList | string, transclude?: Function): ILinkFn;
|
||||||
}
|
}
|
||||||
interface ILinkFn {
|
interface ILinkFn {
|
||||||
(scope: IScope, cloneAttachFn?: Function, options?: ILinkFnOptions): void
|
(scope: IScope, cloneAttachFn?: Function, options?: ILinkFnOptions): void
|
||||||
|
@ -17,7 +17,8 @@ declare namespace angular {
|
||||||
futureParentElement?: Node
|
futureParentElement?: Node
|
||||||
}
|
}
|
||||||
interface IRootScopeService {
|
interface IRootScopeService {
|
||||||
$new(): IScope;
|
$new(isolate?: boolean): IScope;
|
||||||
|
$id: string;
|
||||||
$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;
|
||||||
|
@ -29,19 +30,53 @@ declare namespace angular {
|
||||||
interface IScope extends IRootScopeService {}
|
interface IScope extends IRootScopeService {}
|
||||||
interface IAngularBootstrapConfig {}
|
interface IAngularBootstrapConfig {}
|
||||||
interface IDirective {
|
interface IDirective {
|
||||||
require?: string;
|
compile?: IDirectiveCompileFn;
|
||||||
|
controller?: any;
|
||||||
|
controllerAs?: string;
|
||||||
|
bindToController?: boolean | Object;
|
||||||
|
link?: IDirectiveLinkFn | IDirectivePrePost;
|
||||||
|
name?: string;
|
||||||
|
priority?: number;
|
||||||
|
replace?: boolean;
|
||||||
|
require?: any;
|
||||||
restrict?: string;
|
restrict?: string;
|
||||||
scope?: {[key: string]: string};
|
scope?: any;
|
||||||
link?: {pre?: Function, post?: Function};
|
template?: any;
|
||||||
|
templateUrl?: any;
|
||||||
|
terminal?: boolean;
|
||||||
|
transclude?: any;
|
||||||
|
}
|
||||||
|
interface IDirectiveCompileFn {
|
||||||
|
(templateElement: IAugmentedJQuery, templateAttributes: IAttributes,
|
||||||
|
transclude: ITranscludeFunction): IDirectivePrePost;
|
||||||
|
}
|
||||||
|
interface IDirectivePrePost {
|
||||||
|
pre?: IDirectiveLinkFn;
|
||||||
|
post?: IDirectiveLinkFn;
|
||||||
|
}
|
||||||
|
interface IDirectiveLinkFn {
|
||||||
|
(scope: IScope, instanceElement: IAugmentedJQuery, instanceAttributes: IAttributes,
|
||||||
|
controller: any, transclude: ITranscludeFunction): void;
|
||||||
}
|
}
|
||||||
interface IAttributes {
|
interface IAttributes {
|
||||||
$observe(attr: string, fn: (v: string) => void);
|
$observe(attr: string, fn: (v: string) => void);
|
||||||
}
|
}
|
||||||
interface ITranscludeFunction {}
|
interface ITranscludeFunction {
|
||||||
|
// If the scope is provided, then the cloneAttachFn must be as well.
|
||||||
|
(scope: IScope, cloneAttachFn: ICloneAttachFunction): IAugmentedJQuery;
|
||||||
|
// If one argument is provided, then it's assumed to be the cloneAttachFn.
|
||||||
|
(cloneAttachFn?: ICloneAttachFunction): IAugmentedJQuery;
|
||||||
|
}
|
||||||
|
interface ICloneAttachFunction {
|
||||||
|
// Let's hint but not force cloneAttachFn's signature
|
||||||
|
(clonedElement?: IAugmentedJQuery, scope?: IScope): any;
|
||||||
|
}
|
||||||
interface IAugmentedJQuery {
|
interface IAugmentedJQuery {
|
||||||
bind(name: string, fn: () => void);
|
bind(name: string, fn: () => void);
|
||||||
data(name: string, value?: any);
|
data(name: string, value?: any);
|
||||||
|
inheritedData(name: string, value?: any);
|
||||||
contents(): IAugmentedJQuery;
|
contents(): IAugmentedJQuery;
|
||||||
|
parent(): IAugmentedJQuery;
|
||||||
length: number;
|
length: number;
|
||||||
[index: number]: Node;
|
[index: number]: Node;
|
||||||
}
|
}
|
||||||
|
@ -53,6 +88,19 @@ declare namespace angular {
|
||||||
}
|
}
|
||||||
function element(e: Element): IAugmentedJQuery;
|
function element(e: Element): IAugmentedJQuery;
|
||||||
function bootstrap(e: Element, modules: string[], config: IAngularBootstrapConfig);
|
function bootstrap(e: Element, modules: string[], config: IAngularBootstrapConfig);
|
||||||
|
interface IHttpBackendService {
|
||||||
|
(method: string, url: string, post?: any, callback?: Function, headers?: any, timeout?: number,
|
||||||
|
withCredentials?: boolean): void;
|
||||||
|
}
|
||||||
|
interface ICacheObject {
|
||||||
|
put<T>(key: string, value?: T): T;
|
||||||
|
get(key: string): any;
|
||||||
|
}
|
||||||
|
interface ITemplateCacheService extends ICacheObject {}
|
||||||
|
interface IControllerService {
|
||||||
|
(controllerConstructor: Function, locals?: any, later?: any, ident?: any): any;
|
||||||
|
(controllerName: string, locals?: any): any;
|
||||||
|
}
|
||||||
|
|
||||||
namespace auto {
|
namespace auto {
|
||||||
interface IInjectorService {
|
interface IInjectorService {
|
||||||
|
|
|
@ -4,10 +4,12 @@ export const NG2_INJECTOR = 'ng2.Injector';
|
||||||
export const NG2_PROTO_VIEW_REF_MAP = 'ng2.ProtoViewRefMap';
|
export const NG2_PROTO_VIEW_REF_MAP = 'ng2.ProtoViewRefMap';
|
||||||
export const NG2_ZONE = 'ng2.NgZone';
|
export const NG2_ZONE = 'ng2.NgZone';
|
||||||
|
|
||||||
export const NG1_REQUIRE_INJECTOR_REF = '$' + NG2_INJECTOR + 'Controller';
|
export const NG1_CONTROLLER = '$controller';
|
||||||
export const NG1_SCOPE = '$scope';
|
export const NG1_SCOPE = '$scope';
|
||||||
export const NG1_ROOT_SCOPE = '$rootScope';
|
export const NG1_ROOT_SCOPE = '$rootScope';
|
||||||
export const NG1_COMPILE = '$compile';
|
export const NG1_COMPILE = '$compile';
|
||||||
|
export const NG1_HTTP_BACKEND = '$httpBackend';
|
||||||
export const NG1_INJECTOR = '$injector';
|
export const NG1_INJECTOR = '$injector';
|
||||||
export const NG1_PARSE = '$parse';
|
export const NG1_PARSE = '$parse';
|
||||||
|
export const NG1_TEMPLATE_CACHE = '$templateCache';
|
||||||
export const REQUIRE_INJECTOR = '^' + NG2_INJECTOR;
|
export const REQUIRE_INJECTOR = '^' + NG2_INJECTOR;
|
||||||
|
|
|
@ -101,7 +101,7 @@ export class DowngradeNg2ComponentAdapter {
|
||||||
this.component.onChanges(inputChanges);
|
this.component.onChanges(inputChanges);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.componentScope.$watch(() => this.changeDetector.detectChanges());
|
this.componentScope.$watch(() => this.changeDetector && this.changeDetector.detectChanges());
|
||||||
}
|
}
|
||||||
|
|
||||||
projectContent() {
|
projectContent() {
|
||||||
|
|
|
@ -18,13 +18,12 @@ import {applicationCommonBindings} from 'angular2/src/core/application_ref';
|
||||||
import {compilerProviders} from 'angular2/src/core/compiler/compiler';
|
import {compilerProviders} from 'angular2/src/core/compiler/compiler';
|
||||||
|
|
||||||
import {getComponentInfo, ComponentInfo} from './metadata';
|
import {getComponentInfo, ComponentInfo} from './metadata';
|
||||||
import {onError} from './util';
|
import {onError, controllerKey} from './util';
|
||||||
import {
|
import {
|
||||||
NG1_COMPILE,
|
NG1_COMPILE,
|
||||||
NG1_INJECTOR,
|
NG1_INJECTOR,
|
||||||
NG1_PARSE,
|
NG1_PARSE,
|
||||||
NG1_ROOT_SCOPE,
|
NG1_ROOT_SCOPE,
|
||||||
NG1_REQUIRE_INJECTOR_REF,
|
|
||||||
NG1_SCOPE,
|
NG1_SCOPE,
|
||||||
NG2_APP_VIEW_MANAGER,
|
NG2_APP_VIEW_MANAGER,
|
||||||
NG2_COMPILER,
|
NG2_COMPILER,
|
||||||
|
@ -70,8 +69,10 @@ var upgradeCount: number = 0;
|
||||||
* 7. Whenever an adapter component is instantiated the host element is owned by the the framework
|
* 7. Whenever an adapter component is instantiated the host element is owned by the the framework
|
||||||
* doing the instantiation. The other framework then instantiates and owns the view for that
|
* doing the instantiation. The other framework then instantiates and owns the view for that
|
||||||
* component. This implies that component bindings will always follow the semantics of the
|
* component. This implies that component bindings will always follow the semantics of the
|
||||||
* instantiation framework, but with Angular v2 syntax.
|
* instantiation framework. The syntax is always that of Angular v2 syntax.
|
||||||
* 8. AngularJS v1 is always bootstrapped first and owns the bottom most view.
|
* 8. AngularJS v1 is always bootstrapped first and owns the bottom most view.
|
||||||
|
* 9. The new application is running in Angular v2 zone, and therefore it no longer needs calls to
|
||||||
|
* `$apply()`.
|
||||||
*
|
*
|
||||||
* ## Example
|
* ## Example
|
||||||
*
|
*
|
||||||
|
@ -81,7 +82,7 @@ var upgradeCount: number = 0;
|
||||||
*
|
*
|
||||||
* module.directive('ng1', function() {
|
* module.directive('ng1', function() {
|
||||||
* return {
|
* return {
|
||||||
* scope: { title: '@' },
|
* scope: { title: '=' },
|
||||||
* template: 'ng1[Hello {{title}}!](<span ng-transclude></span>)'
|
* template: 'ng1[Hello {{title}}!](<span ng-transclude></span>)'
|
||||||
* };
|
* };
|
||||||
* });
|
* });
|
||||||
|
@ -127,8 +128,8 @@ export class UpgradeAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
bootstrap(element: Element, modules?: any[],
|
bootstrap(element: Element, modules?: any[],
|
||||||
config?: angular.IAngularBootstrapConfig): UpgradeRef {
|
config?: angular.IAngularBootstrapConfig): UpgradeAdapterRef {
|
||||||
var upgrade = new UpgradeRef();
|
var upgrade = new UpgradeAdapterRef();
|
||||||
var ng1Injector: angular.auto.IInjectorService = null;
|
var ng1Injector: angular.auto.IInjectorService = null;
|
||||||
var platformRef: PlatformRef = platform();
|
var platformRef: PlatformRef = platform();
|
||||||
var applicationRef: ApplicationRef = platformRef.application([
|
var applicationRef: ApplicationRef = platformRef.application([
|
||||||
|
@ -147,6 +148,7 @@ export class UpgradeAdapter {
|
||||||
var rootScope: angular.IRootScopeService;
|
var rootScope: angular.IRootScopeService;
|
||||||
var protoViewRefMap: ProtoViewRefMap = {};
|
var protoViewRefMap: ProtoViewRefMap = {};
|
||||||
var ng1Module = angular.module(this.idPrefix, modules);
|
var ng1Module = angular.module(this.idPrefix, modules);
|
||||||
|
var ng1compilePromise: Promise<any> = null;
|
||||||
ng1Module.value(NG2_INJECTOR, injector)
|
ng1Module.value(NG2_INJECTOR, injector)
|
||||||
.value(NG2_ZONE, ngZone)
|
.value(NG2_ZONE, ngZone)
|
||||||
.value(NG2_COMPILER, compiler)
|
.value(NG2_COMPILER, compiler)
|
||||||
|
@ -176,22 +178,23 @@ export class UpgradeAdapter {
|
||||||
(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());
|
||||||
UpgradeNg1ComponentAdapterBuilder.resolve(this.downgradedComponents, injector);
|
ng1compilePromise =
|
||||||
|
UpgradeNg1ComponentAdapterBuilder.resolve(this.downgradedComponents, injector);
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
angular.element(element).data(NG1_REQUIRE_INJECTOR_REF, injector);
|
angular.element(element).data(controllerKey(NG2_INJECTOR), injector);
|
||||||
ngZone.run(() => { angular.bootstrap(element, [this.idPrefix], config); });
|
ngZone.run(() => { angular.bootstrap(element, [this.idPrefix], config); });
|
||||||
this.compileNg2Components(compiler, protoViewRefMap)
|
Promise.all([this.compileNg2Components(compiler, protoViewRefMap), ng1compilePromise])
|
||||||
.then((protoViewRefMap: ProtoViewRefMap) => {
|
.then(() => {
|
||||||
ngZone.run(() => {
|
ngZone.run(() => {
|
||||||
rootScopePrototype.$apply = original$applyFn; // restore original $apply
|
rootScopePrototype.$apply = original$applyFn; // restore original $apply
|
||||||
while (delayApplyExps.length) {
|
while (delayApplyExps.length) {
|
||||||
rootScope.$apply(delayApplyExps.shift());
|
rootScope.$apply(delayApplyExps.shift());
|
||||||
}
|
}
|
||||||
upgrade.readyFn && upgrade.readyFn();
|
(<any>upgrade)._bootstrapDone(applicationRef, ng1Injector);
|
||||||
});
|
});
|
||||||
});
|
}, onError);
|
||||||
return upgrade;
|
return upgrade;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,7 +217,7 @@ export class UpgradeAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ProtoViewRefMap {
|
interface ProtoViewRefMap {
|
||||||
[selector: string]: ProtoViewRef
|
[selector: string]: ProtoViewRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ng1ComponentDirective(info: ComponentInfo, idPrefix: string): Function {
|
function ng1ComponentDirective(info: ComponentInfo, idPrefix: string): Function {
|
||||||
|
@ -246,8 +249,38 @@ function ng1ComponentDirective(info: ComponentInfo, idPrefix: string): Function
|
||||||
return directiveFactory;
|
return directiveFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UpgradeRef {
|
/**
|
||||||
readyFn: Function;
|
* Use `UgradeAdapterRef` to control a hybrid AngularJS v1 / Angular v2 application.
|
||||||
|
*/
|
||||||
|
export class UpgradeAdapterRef {
|
||||||
|
/* @internal */
|
||||||
|
private _readyFn: (upgradeAdapterRef?: UpgradeAdapterRef) => void = null;
|
||||||
|
|
||||||
ready(fn: Function) { this.readyFn = fn; }
|
public applicationRef: ApplicationRef = null;
|
||||||
|
public ng1Injector: angular.auto.IInjectorService = null;
|
||||||
|
|
||||||
|
/* @internal */
|
||||||
|
private _bootstrapDone(applicationRef: ApplicationRef,
|
||||||
|
ng1Injector: angular.auto.IInjectorService) {
|
||||||
|
this.applicationRef = applicationRef;
|
||||||
|
this.ng1Injector = ng1Injector;
|
||||||
|
this._readyFn && this._readyFn(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a callback function which is notified upon successful hybrid AngularJS v1 / Angular v2
|
||||||
|
* application has been bootstrapped.
|
||||||
|
*
|
||||||
|
* The `ready` callback function is invoked inside the Angular v2 zone, therefore it does not
|
||||||
|
* require a call to `$apply()`.
|
||||||
|
*/
|
||||||
|
public ready(fn: (upgradeAdapterRef?: UpgradeAdapterRef) => void) { this._readyFn = fn; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispose of running hybrid AngularJS v1 / Angular v2 application.
|
||||||
|
*/
|
||||||
|
public dispose() {
|
||||||
|
this.ng1Injector.get(NG1_ROOT_SCOPE).$destroy();
|
||||||
|
this.applicationRef.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,20 @@ import {
|
||||||
SimpleChange,
|
SimpleChange,
|
||||||
Type
|
Type
|
||||||
} from 'angular2/angular2';
|
} from 'angular2/angular2';
|
||||||
import {NG1_COMPILE, NG1_SCOPE} from './constants';
|
import {
|
||||||
|
NG1_COMPILE,
|
||||||
|
NG1_SCOPE,
|
||||||
|
NG1_HTTP_BACKEND,
|
||||||
|
NG1_TEMPLATE_CACHE,
|
||||||
|
NG1_CONTROLLER
|
||||||
|
} from './constants';
|
||||||
|
import {controllerKey} from './util';
|
||||||
|
|
||||||
const CAMEL_CASE = /([A-Z])/g;
|
const CAMEL_CASE = /([A-Z])/g;
|
||||||
const INITIAL_VALUE = {
|
const INITIAL_VALUE = {
|
||||||
__UNINITIALIZED__: true
|
__UNINITIALIZED__: true
|
||||||
};
|
};
|
||||||
|
const NOT_SUPPORTED: any = 'NOT_SUPPORTED';
|
||||||
|
|
||||||
|
|
||||||
export class UpgradeNg1ComponentAdapterBuilder {
|
export class UpgradeNg1ComponentAdapterBuilder {
|
||||||
|
@ -25,6 +33,9 @@ export class UpgradeNg1ComponentAdapterBuilder {
|
||||||
propertyOutputs: string[] = [];
|
propertyOutputs: string[] = [];
|
||||||
checkProperties: string[] = [];
|
checkProperties: string[] = [];
|
||||||
propertyMap: {[name: string]: string} = {};
|
propertyMap: {[name: string]: string} = {};
|
||||||
|
linkFn: angular.ILinkFn = null;
|
||||||
|
directive: angular.IDirective = null;
|
||||||
|
$controller: angular.IControllerService = null;
|
||||||
|
|
||||||
constructor(public name: string) {
|
constructor(public name: string) {
|
||||||
var selector = name.replace(CAMEL_CASE, (all, next: string) => '-' + next.toLowerCase());
|
var selector = name.replace(CAMEL_CASE, (all, next: string) => '-' + next.toLowerCase());
|
||||||
|
@ -33,14 +44,12 @@ export class UpgradeNg1ComponentAdapterBuilder {
|
||||||
Directive({selector: selector, inputs: this.inputsRename, outputs: this.outputsRename})
|
Directive({selector: selector, inputs: this.inputsRename, outputs: this.outputsRename})
|
||||||
.Class({
|
.Class({
|
||||||
constructor: [
|
constructor: [
|
||||||
new Inject(NG1_COMPILE),
|
|
||||||
new Inject(NG1_SCOPE),
|
new Inject(NG1_SCOPE),
|
||||||
ElementRef,
|
ElementRef,
|
||||||
function(compile: angular.ICompileService, scope: angular.IScope,
|
function(scope: angular.IScope, elementRef: ElementRef) {
|
||||||
elementRef: ElementRef) {
|
return new UpgradeNg1ComponentAdapter(
|
||||||
return new UpgradeNg1ComponentAdapter(compile, scope, elementRef, self.inputs,
|
self.linkFn, scope, self.directive, elementRef, self.$controller, self.inputs,
|
||||||
self.outputs, self.propertyOutputs,
|
self.outputs, self.propertyOutputs, self.checkProperties, self.propertyMap);
|
||||||
self.checkProperties, self.propertyMap);
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
onChanges: function() { /* needs to be here for ng2 to properly detect it */ },
|
onChanges: function() { /* needs to be here for ng2 to properly detect it */ },
|
||||||
|
@ -48,13 +57,27 @@ export class UpgradeNg1ComponentAdapterBuilder {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
extractBindings(injector: angular.auto.IInjectorService) {
|
extractDirective(injector: angular.auto.IInjectorService): angular.IDirective {
|
||||||
var directives: angular.IDirective[] = injector.get(this.name + 'Directive');
|
var directives: angular.IDirective[] = injector.get(this.name + 'Directive');
|
||||||
if (directives.length > 1) {
|
if (directives.length > 1) {
|
||||||
throw new Error('Only support single directive definition for: ' + this.name);
|
throw new Error('Only support single directive definition for: ' + this.name);
|
||||||
}
|
}
|
||||||
var directive = directives[0];
|
var directive = directives[0];
|
||||||
var scope = directive.scope;
|
if (directive.replace) this.notSupported('replace');
|
||||||
|
if (directive.terminal) this.notSupported('terminal');
|
||||||
|
var link = directive.link;
|
||||||
|
if (typeof link == 'object') {
|
||||||
|
if ((<angular.IDirectivePrePost>link).post) this.notSupported('link.post');
|
||||||
|
}
|
||||||
|
return directive;
|
||||||
|
}
|
||||||
|
|
||||||
|
private notSupported(feature: string) {
|
||||||
|
throw new Error(`Upgraded directive '${this.name}' does not support '${feature}'.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
extractBindings() {
|
||||||
|
var scope = this.directive.scope;
|
||||||
if (typeof scope == 'object') {
|
if (typeof scope == 'object') {
|
||||||
for (var name in scope) {
|
for (var name in scope) {
|
||||||
if ((<any>scope).hasOwnProperty(name)) {
|
if ((<any>scope).hasOwnProperty(name)) {
|
||||||
|
@ -94,25 +117,66 @@ export class UpgradeNg1ComponentAdapterBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compileTemplate(compile: angular.ICompileService, templateCache: angular.ITemplateCacheService,
|
||||||
|
httpBackend: angular.IHttpBackendService): Promise<any> {
|
||||||
|
if (this.directive.template) {
|
||||||
|
this.linkFn = compileHtml(this.directive.template);
|
||||||
|
} else if (this.directive.templateUrl) {
|
||||||
|
var url = this.directive.templateUrl;
|
||||||
|
var html = templateCache.get(url);
|
||||||
|
if (html !== undefined) {
|
||||||
|
this.linkFn = compileHtml(html);
|
||||||
|
} else {
|
||||||
|
return new Promise((resolve, err) => {
|
||||||
|
httpBackend('GET', url, null, (status, response) => {
|
||||||
|
if (status == 200) {
|
||||||
|
resolve(this.linkFn = compileHtml(templateCache.put(url, response)));
|
||||||
|
} else {
|
||||||
|
err(`GET ${url} returned ${status}: ${response}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(`Directive '${this.name}' is not a component, it is missing template.`);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
function compileHtml(html) {
|
||||||
|
var div = document.createElement('div');
|
||||||
|
div.innerHTML = html;
|
||||||
|
return compile(div.childNodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static resolve(exportedComponents: {[name: string]: UpgradeNg1ComponentAdapterBuilder},
|
static resolve(exportedComponents: {[name: string]: UpgradeNg1ComponentAdapterBuilder},
|
||||||
injector: angular.auto.IInjectorService) {
|
injector: angular.auto.IInjectorService): Promise<any> {
|
||||||
|
var promises = [];
|
||||||
|
var compile: angular.ICompileService = injector.get(NG1_COMPILE);
|
||||||
|
var templateCache: angular.ITemplateCacheService = injector.get(NG1_TEMPLATE_CACHE);
|
||||||
|
var httpBackend: angular.IHttpBackendService = injector.get(NG1_HTTP_BACKEND);
|
||||||
|
var $controller: angular.IControllerService = injector.get(NG1_CONTROLLER);
|
||||||
for (var name in exportedComponents) {
|
for (var name in exportedComponents) {
|
||||||
if ((<any>exportedComponents).hasOwnProperty(name)) {
|
if ((<any>exportedComponents).hasOwnProperty(name)) {
|
||||||
var exportedComponent = exportedComponents[name];
|
var exportedComponent = exportedComponents[name];
|
||||||
exportedComponent.extractBindings(injector);
|
exportedComponent.directive = exportedComponent.extractDirective(injector);
|
||||||
|
exportedComponent.$controller = $controller;
|
||||||
|
exportedComponent.extractBindings();
|
||||||
|
var promise = exportedComponent.compileTemplate(compile, templateCache, httpBackend);
|
||||||
|
if (promise) promises.push(promise)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return Promise.all(promises);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class UpgradeNg1ComponentAdapter implements OnChanges, DoCheck {
|
class UpgradeNg1ComponentAdapter implements OnChanges, DoCheck {
|
||||||
componentScope: angular.IScope = null;
|
destinationObj: any = null;
|
||||||
checkLastValues: any[] = [];
|
checkLastValues: any[] = [];
|
||||||
|
|
||||||
constructor(compile: angular.ICompileService, scope: angular.IScope, elementRef: ElementRef,
|
constructor(linkFn: angular.ILinkFn, scope: angular.IScope, private directive: angular.IDirective,
|
||||||
|
elementRef: ElementRef, $controller: angular.IControllerService,
|
||||||
private inputs: string[], private outputs: string[], private propOuts: string[],
|
private inputs: string[], private outputs: string[], private propOuts: string[],
|
||||||
private checkProperties: string[], private propertyMap: {[key: string]: string}) {
|
private checkProperties: string[], private propertyMap: {[key: string]: string}) {
|
||||||
var chailTail = scope.$$childTail; // remember where the next scope is inserted
|
|
||||||
var element: Element = elementRef.nativeElement;
|
var element: Element = elementRef.nativeElement;
|
||||||
var childNodes: Node[] = [];
|
var childNodes: Node[] = [];
|
||||||
var childNode;
|
var childNode;
|
||||||
|
@ -120,11 +184,31 @@ class UpgradeNg1ComponentAdapter implements OnChanges, DoCheck {
|
||||||
element.removeChild(childNode);
|
element.removeChild(childNode);
|
||||||
childNodes.push(childNode);
|
childNodes.push(childNode);
|
||||||
}
|
}
|
||||||
element.appendChild(element.ownerDocument.createElement('ng-transclude'));
|
var componentScope = scope.$new(!!directive.scope);
|
||||||
compile(element)(scope, null,
|
var $element = angular.element(element);
|
||||||
{parentBoundTranscludeFn: (scope, cloneAttach) => cloneAttach(childNodes)});
|
var controllerType = directive.controller;
|
||||||
// If we are first scope take it, otherwise take the next one in list.
|
var controller: any = null;
|
||||||
this.componentScope = chailTail ? chailTail.$$nextSibling : scope.$$childHead;
|
if (controllerType) {
|
||||||
|
var locals = {$scope: componentScope, $element: $element};
|
||||||
|
controller = $controller(controllerType, locals, null, directive.controllerAs);
|
||||||
|
$element.data(controllerKey(directive.name), controller);
|
||||||
|
}
|
||||||
|
var link = directive.link;
|
||||||
|
if (typeof link == 'object') link = (<angular.IDirectivePrePost>link).pre;
|
||||||
|
if (link) {
|
||||||
|
var attrs: angular.IAttributes = NOT_SUPPORTED;
|
||||||
|
var transcludeFn: angular.ITranscludeFunction = NOT_SUPPORTED;
|
||||||
|
var linkController = this.resolveRequired($element, directive.require);
|
||||||
|
(<angular.IDirectiveLinkFn>directive.link)(componentScope, $element, attrs, linkController,
|
||||||
|
transcludeFn);
|
||||||
|
}
|
||||||
|
this.destinationObj = directive.bindToController && controller ? controller : componentScope;
|
||||||
|
|
||||||
|
linkFn(componentScope, (clonedElement: Node[], scope: angular.IScope) => {
|
||||||
|
for (var i = 0, ii = clonedElement.length; i < ii; i++) {
|
||||||
|
element.appendChild(clonedElement[i]);
|
||||||
|
}
|
||||||
|
}, {parentBoundTranscludeFn: (scope, cloneAttach) => { cloneAttach(childNodes) }});
|
||||||
|
|
||||||
for (var i = 0; i < inputs.length; i++) {
|
for (var i = 0; i < inputs.length; i++) {
|
||||||
this[inputs[i]] = null;
|
this[inputs[i]] = null;
|
||||||
|
@ -150,11 +234,11 @@ class UpgradeNg1ComponentAdapter implements OnChanges, DoCheck {
|
||||||
|
|
||||||
doCheck() {
|
doCheck() {
|
||||||
var count = 0;
|
var count = 0;
|
||||||
var scope = this.componentScope;
|
var destinationObj = this.destinationObj;
|
||||||
var lastValues = this.checkLastValues;
|
var lastValues = this.checkLastValues;
|
||||||
var checkProperties = this.checkProperties;
|
var checkProperties = this.checkProperties;
|
||||||
for (var i = 0; i < checkProperties.length; i++) {
|
for (var i = 0; i < checkProperties.length; i++) {
|
||||||
var value = scope[checkProperties[i]];
|
var value = destinationObj[checkProperties[i]];
|
||||||
var last = lastValues[i];
|
var last = lastValues[i];
|
||||||
if (value !== last) {
|
if (value !== last) {
|
||||||
if (typeof value == 'number' && isNaN(value) && typeof last == 'number' && isNaN(last)) {
|
if (typeof value == 'number' && isNaN(value) && typeof last == 'number' && isNaN(last)) {
|
||||||
|
@ -169,6 +253,46 @@ class UpgradeNg1ComponentAdapter implements OnChanges, DoCheck {
|
||||||
}
|
}
|
||||||
|
|
||||||
setComponentProperty(name: string, value: any) {
|
setComponentProperty(name: string, value: any) {
|
||||||
this.componentScope[this.propertyMap[name]] = value;
|
this.destinationObj[this.propertyMap[name]] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private resolveRequired($element: angular.IAugmentedJQuery, require: string | string[]): any {
|
||||||
|
if (!require) {
|
||||||
|
return undefined;
|
||||||
|
} else if (typeof require == 'string') {
|
||||||
|
var name: string = <string>require;
|
||||||
|
var isOptional = false;
|
||||||
|
var startParent = false;
|
||||||
|
var searchParents = false;
|
||||||
|
var ch: string;
|
||||||
|
if (name.charAt(0) == '?') {
|
||||||
|
isOptional = true;
|
||||||
|
name = name.substr(1);
|
||||||
|
}
|
||||||
|
if (name.charAt(0) == '^') {
|
||||||
|
searchParents = true;
|
||||||
|
name = name.substr(1);
|
||||||
|
}
|
||||||
|
if (name.charAt(0) == '^') {
|
||||||
|
startParent = true;
|
||||||
|
name = name.substr(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = controllerKey(name);
|
||||||
|
if (startParent) $element = $element.parent();
|
||||||
|
var dep = searchParents ? $element.inheritedData(key) : $element.data(key);
|
||||||
|
if (!dep && !isOptional) {
|
||||||
|
throw new Error(`Can not locate '${require}' in '${this.directive.name}'.`);
|
||||||
|
}
|
||||||
|
return dep;
|
||||||
|
} else if (require instanceof Array) {
|
||||||
|
var deps = [];
|
||||||
|
for (var i = 0; i < require.length; i++) {
|
||||||
|
deps.push(this.resolveRequired($element, require[i]));
|
||||||
|
}
|
||||||
|
return deps;
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
`Directive '${this.directive.name}' require syntax unrecognized: ${this.directive.require}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,3 +10,7 @@ export function onError(e: any) {
|
||||||
console.log(e, e.stack);
|
console.log(e, e.stack);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function controllerKey(name: string): string {
|
||||||
|
return '$' + name + 'Controller';
|
||||||
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
xit,
|
xit,
|
||||||
} from 'angular2/testing_internal';
|
} from 'angular2/testing_internal';
|
||||||
|
|
||||||
import {Component, Inject, EventEmitter} from 'angular2/angular2';
|
import {Component, Class, Inject, EventEmitter} from 'angular2/angular2';
|
||||||
import {UpgradeAdapter} from 'upgrade/upgrade';
|
import {UpgradeAdapter} from 'upgrade/upgrade';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
|
@ -29,8 +29,9 @@ export function main() {
|
||||||
var adapter: UpgradeAdapter = new UpgradeAdapter();
|
var adapter: UpgradeAdapter = new UpgradeAdapter();
|
||||||
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||||
adapter.bootstrap(element, ['ng1'])
|
adapter.bootstrap(element, ['ng1'])
|
||||||
.ready(() => {
|
.ready((ref) => {
|
||||||
expect(document.body.textContent).toEqual("ng1[NG2(~ng-content~)]");
|
expect(document.body.textContent).toEqual("ng1[NG2(~ng-content~)]");
|
||||||
|
ref.dispose();
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
@ -54,8 +55,9 @@ export function main() {
|
||||||
var element = html("<div>{{'ng1('}}<ng2></ng2>{{')'}}</div>");
|
var element = html("<div>{{'ng1('}}<ng2></ng2>{{')'}}</div>");
|
||||||
|
|
||||||
adapter.bootstrap(element, ['ng1'])
|
adapter.bootstrap(element, ['ng1'])
|
||||||
.ready(() => {
|
.ready((ref) => {
|
||||||
expect(document.body.textContent).toEqual("ng1(ng2(ng1(transclude)))");
|
expect(document.body.textContent).toEqual("ng1(ng2(ng1(transclude)))");
|
||||||
|
ref.dispose();
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
@ -90,16 +92,17 @@ export function main() {
|
||||||
|
|
||||||
var element = html("<div>{{reset(); l('1A');}}<ng2>{{l('1B')}}</ng2>{{l('1C')}}</div>");
|
var element = html("<div>{{reset(); l('1A');}}<ng2>{{l('1B')}}</ng2>{{l('1C')}}</div>");
|
||||||
adapter.bootstrap(element, ['ng1'])
|
adapter.bootstrap(element, ['ng1'])
|
||||||
.ready(() => {
|
.ready((ref) => {
|
||||||
expect(document.body.textContent).toEqual("1A;2A;ng1a;2B;ng1b;2C;1C;");
|
expect(document.body.textContent).toEqual("1A;2A;ng1a;2B;ng1b;2C;1C;");
|
||||||
// https://github.com/angular/angular.js/issues/12983
|
// https://github.com/angular/angular.js/issues/12983
|
||||||
expect(log).toEqual(['1A', '1B', '1C', '2A', '2B', '2C', 'ng1a', 'ng1b']);
|
expect(log).toEqual(['1A', '1B', '1C', '2A', '2B', '2C', 'ng1a', 'ng1b']);
|
||||||
|
ref.dispose();
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('binding from ng1 to ng2', () => {
|
describe('downgrade ng2 component', () => {
|
||||||
it('should bind properties, events', inject([AsyncTestCompleter], (async) => {
|
it('should bind properties, events', inject([AsyncTestCompleter], (async) => {
|
||||||
var adapter: UpgradeAdapter = new UpgradeAdapter();
|
var adapter: UpgradeAdapter = new UpgradeAdapter();
|
||||||
var ng1Module = angular.module('ng1', []);
|
var ng1Module = angular.module('ng1', []);
|
||||||
|
@ -197,7 +200,7 @@ export function main() {
|
||||||
| modelA: {{modelA}}; modelB: {{modelB}}; eventA: {{eventA}}; eventB: {{eventB}};
|
| modelA: {{modelA}}; modelB: {{modelB}}; eventA: {{eventA}}; eventB: {{eventB}};
|
||||||
</div>`);
|
</div>`);
|
||||||
adapter.bootstrap(element, ['ng1'])
|
adapter.bootstrap(element, ['ng1'])
|
||||||
.ready(() => {
|
.ready((ref) => {
|
||||||
expect(multiTrim(document.body.textContent))
|
expect(multiTrim(document.body.textContent))
|
||||||
.toEqual(
|
.toEqual(
|
||||||
"ignore: -; " + "literal: Text; interpolate: Hello world; " +
|
"ignore: -; " + "literal: Text; interpolate: Hello world; " +
|
||||||
|
@ -210,6 +213,7 @@ export function main() {
|
||||||
.toEqual("ignore: -; " + "literal: Text; interpolate: Hello world; " +
|
.toEqual("ignore: -; " + "literal: Text; interpolate: Hello world; " +
|
||||||
"oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (3) | " +
|
"oneWayA: A; oneWayB: B; twoWayA: newA; twoWayB: newB; (3) | " +
|
||||||
"modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;");
|
"modelA: newA; modelB: newB; eventA: aFired; eventB: bFired;");
|
||||||
|
ref.dispose();
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -217,7 +221,7 @@ export function main() {
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('binding from ng2 to ng1', () => {
|
describe('upgrade ng1 component', () => {
|
||||||
it('should bind properties, events', inject([AsyncTestCompleter], (async) => {
|
it('should bind properties, events', inject([AsyncTestCompleter], (async) => {
|
||||||
var adapter = new UpgradeAdapter();
|
var adapter = new UpgradeAdapter();
|
||||||
var ng1Module = angular.module('ng1', []);
|
var ng1Module = angular.module('ng1', []);
|
||||||
|
@ -225,19 +229,19 @@ export function main() {
|
||||||
var ng1 = function() {
|
var ng1 = function() {
|
||||||
return {
|
return {
|
||||||
template: 'Hello {{fullName}}; A: {{dataA}}; B: {{dataB}}; | ',
|
template: 'Hello {{fullName}}; A: {{dataA}}; B: {{dataB}}; | ',
|
||||||
scope: {fullName: '@', modelA: '=dataA', modelB: '=dataB', event: '&'},
|
scope: {fullName: '@', modelA: '=dataA', modelB: '=dataB', event: '&'},
|
||||||
link: function(scope) {
|
link: function(scope) {
|
||||||
scope.$watch('dataB', (v) => {
|
scope.$watch('dataB', (v) => {
|
||||||
if (v == 'Savkin') {
|
if (v == 'Savkin') {
|
||||||
scope.dataB = 'SAVKIN';
|
scope.dataB = 'SAVKIN';
|
||||||
scope.event('WORKS');
|
scope.event('WORKS');
|
||||||
|
|
||||||
// Should not update becaus [model-a] is uni directional
|
// Should not update becaus [model-a] is uni directional
|
||||||
scope.dataA = 'VICTOR';
|
scope.dataA = 'VICTOR';
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
};
|
};
|
||||||
ng1Module.directive('ng1', ng1);
|
ng1Module.directive('ng1', ng1);
|
||||||
var Ng2 =
|
var Ng2 =
|
||||||
|
@ -260,17 +264,208 @@ export function main() {
|
||||||
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||||
var element = html(`<div><ng2></ng2></div>`);
|
var element = html(`<div><ng2></ng2></div>`);
|
||||||
adapter.bootstrap(element, ['ng1'])
|
adapter.bootstrap(element, ['ng1'])
|
||||||
.ready(() => {
|
.ready((ref) => {
|
||||||
// we need to do setTimeout, because the EventEmitter uses setTimeout to schedule
|
// we need to do setTimeout, because the EventEmitter uses setTimeout to schedule
|
||||||
// events, and so without this we would not see the events processed.
|
// events, and so without this we would not see the events processed.
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
expect(multiTrim(document.body.textContent))
|
expect(multiTrim(document.body.textContent))
|
||||||
.toEqual(
|
.toEqual(
|
||||||
"Hello SAVKIN, Victor; A: VICTOR; B: SAVKIN; | Hello TEST; A: First; B: Last; | WORKS-SAVKIN, Victor");
|
"Hello SAVKIN, Victor; A: VICTOR; B: SAVKIN; | Hello TEST; A: First; B: Last; | WORKS-SAVKIN, Victor");
|
||||||
|
ref.dispose();
|
||||||
async.done();
|
async.done();
|
||||||
}, 0);
|
}, 0);
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('should support templateUrl fetched from $httpBackend',
|
||||||
|
inject([AsyncTestCompleter], (async) => {
|
||||||
|
var adapter = new UpgradeAdapter();
|
||||||
|
var ng1Module = angular.module('ng1', []);
|
||||||
|
ng1Module.value('$httpBackend',
|
||||||
|
(method, url, post, cbFn) => { cbFn(200, `${method}:${url}`); });
|
||||||
|
|
||||||
|
var ng1 = function() { return {templateUrl: 'url.html'}; };
|
||||||
|
ng1Module.directive('ng1', ng1);
|
||||||
|
var Ng2 = Component({
|
||||||
|
selector: 'ng2',
|
||||||
|
template: '<ng1></ng1>',
|
||||||
|
directives: [adapter.upgradeNg1Component('ng1')]
|
||||||
|
}).Class({constructor: function() {}});
|
||||||
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||||
|
var element = html(`<div><ng2></ng2></div>`);
|
||||||
|
adapter.bootstrap(element, ['ng1'])
|
||||||
|
.ready((ref) => {
|
||||||
|
expect(multiTrim(document.body.textContent)).toEqual('GET:url.html');
|
||||||
|
ref.dispose();
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should support templateUrl fetched from $templateCache',
|
||||||
|
inject([AsyncTestCompleter], (async) => {
|
||||||
|
var adapter = new UpgradeAdapter();
|
||||||
|
var ng1Module = angular.module('ng1', []);
|
||||||
|
ng1Module.run(($templateCache) => $templateCache.put('url.html', 'WORKS'));
|
||||||
|
|
||||||
|
var ng1 = function() { return {templateUrl: 'url.html'}; };
|
||||||
|
ng1Module.directive('ng1', ng1);
|
||||||
|
var Ng2 = Component({
|
||||||
|
selector: 'ng2',
|
||||||
|
template: '<ng1></ng1>',
|
||||||
|
directives: [adapter.upgradeNg1Component('ng1')]
|
||||||
|
}).Class({constructor: function() {}});
|
||||||
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||||
|
var element = html(`<div><ng2></ng2></div>`);
|
||||||
|
adapter.bootstrap(element, ['ng1'])
|
||||||
|
.ready((ref) => {
|
||||||
|
expect(multiTrim(document.body.textContent)).toEqual('WORKS');
|
||||||
|
ref.dispose();
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should support controller with controllerAs', inject([AsyncTestCompleter], (async) => {
|
||||||
|
var adapter = new UpgradeAdapter();
|
||||||
|
var ng1Module = angular.module('ng1', []);
|
||||||
|
|
||||||
|
var ng1 = function() {
|
||||||
|
return {
|
||||||
|
scope: true,
|
||||||
|
template:
|
||||||
|
'{{ctl.scope}}; {{ctl.isClass}}; {{ctl.hasElement}}; {{ctl.isPublished()}}',
|
||||||
|
controllerAs: 'ctl',
|
||||||
|
controller: Class({
|
||||||
|
constructor: function($scope, $element) {
|
||||||
|
(<any>this).verifyIAmAClass();
|
||||||
|
this.scope = $scope.$parent.$parent == $scope.$root ? 'scope' : 'wrong-scope';
|
||||||
|
this.hasElement = $element[0].nodeName;
|
||||||
|
this.$element = $element;
|
||||||
|
},
|
||||||
|
verifyIAmAClass: function() { this.isClass = 'isClass'; },
|
||||||
|
isPublished: function() {
|
||||||
|
return this.$element.controller('ng1') == this ? 'published' : 'not-published';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
};
|
||||||
|
ng1Module.directive('ng1', ng1);
|
||||||
|
var Ng2 = Component({
|
||||||
|
selector: 'ng2',
|
||||||
|
template: '<ng1></ng1>',
|
||||||
|
directives: [adapter.upgradeNg1Component('ng1')]
|
||||||
|
}).Class({constructor: function() {}});
|
||||||
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||||
|
var element = html(`<div><ng2></ng2></div>`);
|
||||||
|
adapter.bootstrap(element, ['ng1'])
|
||||||
|
.ready((ref) => {
|
||||||
|
expect(multiTrim(document.body.textContent))
|
||||||
|
.toEqual('scope; isClass; NG1; published');
|
||||||
|
ref.dispose();
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should support bindToController', inject([AsyncTestCompleter], (async) => {
|
||||||
|
var adapter = new UpgradeAdapter();
|
||||||
|
var ng1Module = angular.module('ng1', []);
|
||||||
|
|
||||||
|
var ng1 = function() {
|
||||||
|
return {
|
||||||
|
scope: {title: '@'},
|
||||||
|
bindToController: true, template: '{{ctl.title}}',
|
||||||
|
controllerAs: 'ctl',
|
||||||
|
controller: Class({constructor: function() {}})
|
||||||
|
};
|
||||||
|
};
|
||||||
|
ng1Module.directive('ng1', ng1);
|
||||||
|
var Ng2 = Component({
|
||||||
|
selector: 'ng2',
|
||||||
|
template: '<ng1 title="WORKS"></ng1>',
|
||||||
|
directives: [adapter.upgradeNg1Component('ng1')]
|
||||||
|
}).Class({constructor: function() {}});
|
||||||
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||||
|
var element = html(`<div><ng2></ng2></div>`);
|
||||||
|
adapter.bootstrap(element, ['ng1'])
|
||||||
|
.ready((ref) => {
|
||||||
|
expect(multiTrim(document.body.textContent)).toEqual('WORKS');
|
||||||
|
ref.dispose();
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should support single require in linking fn', inject([AsyncTestCompleter], (async) => {
|
||||||
|
var adapter = new UpgradeAdapter();
|
||||||
|
var ng1Module = angular.module('ng1', []);
|
||||||
|
|
||||||
|
var ng1 = function($rootScope) {
|
||||||
|
return {
|
||||||
|
scope: {title: '@'},
|
||||||
|
bindToController: true, template: '{{ctl.status}}',
|
||||||
|
require: 'ng1',
|
||||||
|
controller: Class({constructor: function() { this.status = 'WORKS'; }}),
|
||||||
|
link: function(scope, element, attrs, linkController) {
|
||||||
|
expect(scope.$root).toEqual($rootScope);
|
||||||
|
expect(element[0].nodeName).toEqual('NG1');
|
||||||
|
expect(linkController.status).toEqual('WORKS');
|
||||||
|
scope.ctl = linkController;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
ng1Module.directive('ng1', ng1);
|
||||||
|
var Ng2 = Component({
|
||||||
|
selector: 'ng2',
|
||||||
|
template: '<ng1></ng1>',
|
||||||
|
directives: [adapter.upgradeNg1Component('ng1')]
|
||||||
|
}).Class({constructor: function() {}});
|
||||||
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||||
|
var element = html(`<div><ng2></ng2></div>`);
|
||||||
|
adapter.bootstrap(element, ['ng1'])
|
||||||
|
.ready((ref) => {
|
||||||
|
expect(multiTrim(document.body.textContent)).toEqual('WORKS');
|
||||||
|
ref.dispose();
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should support array require in linking fn', inject([AsyncTestCompleter], (async) => {
|
||||||
|
var adapter = new UpgradeAdapter();
|
||||||
|
var ng1Module = angular.module('ng1', []);
|
||||||
|
|
||||||
|
var parent = function() {
|
||||||
|
return {controller: Class({constructor: function() { this.parent = 'PARENT'; }})};
|
||||||
|
};
|
||||||
|
var ng1 = function() {
|
||||||
|
return {
|
||||||
|
scope: {title: '@'},
|
||||||
|
bindToController: true, template: '{{parent.parent}}:{{ng1.status}}',
|
||||||
|
require: ['ng1', '^parent', '?^^notFound'],
|
||||||
|
controller: Class({constructor: function() { this.status = 'WORKS'; }}),
|
||||||
|
link: function(scope, element, attrs, linkControllers) {
|
||||||
|
expect(linkControllers[0].status).toEqual('WORKS');
|
||||||
|
expect(linkControllers[1].parent).toEqual('PARENT');
|
||||||
|
expect(linkControllers[2]).toBe(undefined);
|
||||||
|
scope.ng1 = linkControllers[0];
|
||||||
|
scope.parent = linkControllers[1];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
ng1Module.directive('parent', parent);
|
||||||
|
ng1Module.directive('ng1', ng1);
|
||||||
|
var Ng2 = Component({
|
||||||
|
selector: 'ng2',
|
||||||
|
template: '<ng1></ng1>',
|
||||||
|
directives: [adapter.upgradeNg1Component('ng1')]
|
||||||
|
}).Class({constructor: function() {}});
|
||||||
|
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||||
|
var element = html(`<div><parent><ng2></ng2></parent></div>`);
|
||||||
|
adapter.bootstrap(element, ['ng1'])
|
||||||
|
.ready((ref) => {
|
||||||
|
expect(multiTrim(document.body.textContent)).toEqual('PARENT:WORKS');
|
||||||
|
ref.dispose();
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('examples', () => {
|
describe('examples', () => {
|
||||||
|
@ -280,7 +475,7 @@ export function main() {
|
||||||
|
|
||||||
module.directive('ng1', function() {
|
module.directive('ng1', function() {
|
||||||
return {
|
return {
|
||||||
scope: {title: '@'},
|
scope: {title: '='},
|
||||||
transclude: true, template: 'ng1[Hello {{title}}!](<span ng-transclude></span>)'
|
transclude: true, template: 'ng1[Hello {{title}}!](<span ng-transclude></span>)'
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -299,9 +494,10 @@ export function main() {
|
||||||
document.body.innerHTML = '<ng2 name="World">project</ng2>';
|
document.body.innerHTML = '<ng2 name="World">project</ng2>';
|
||||||
|
|
||||||
adapter.bootstrap(document.body, ['myExample'])
|
adapter.bootstrap(document.body, ['myExample'])
|
||||||
.ready(function() {
|
.ready((ref) => {
|
||||||
expect(multiTrim(document.body.textContent))
|
expect(multiTrim(document.body.textContent))
|
||||||
.toEqual("ng2[ng1[Hello World!](transclude)](project)");
|
.toEqual("ng2[ng1[Hello World!](transclude)](project)");
|
||||||
|
ref.dispose();
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
Loading…
Reference in New Issue