feat(ngUpgrade): support for content project from ng1->ng2
This commit is contained in:
parent
867c08ac84
commit
cd90e6ed8f
|
@ -50,6 +50,8 @@ export abstract class DomRenderer extends Renderer implements NodeFactory<Node>
|
||||||
|
|
||||||
abstract destroyView(viewRef: RenderViewRef);
|
abstract destroyView(viewRef: RenderViewRef);
|
||||||
|
|
||||||
|
abstract createRootContentInsertionPoint();
|
||||||
|
|
||||||
getNativeElementSync(location: RenderElementRef): any {
|
getNativeElementSync(location: RenderElementRef): any {
|
||||||
return resolveInternalDomView(location.renderView).boundElements[location.boundElementIndex];
|
return resolveInternalDomView(location.renderView).boundElements[location.boundElementIndex];
|
||||||
}
|
}
|
||||||
|
@ -282,6 +284,9 @@ export class DomRenderer_ extends DomRenderer {
|
||||||
DOM.setAttribute(node, attrNameAndValues[attrIdx], attrNameAndValues[attrIdx + 1]);
|
DOM.setAttribute(node, attrNameAndValues[attrIdx], attrNameAndValues[attrIdx + 1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
createRootContentInsertionPoint(): Node {
|
||||||
|
return DOM.createComment('root-content-insertion-point');
|
||||||
|
}
|
||||||
createShadowRoot(host: Node, templateId: number): Node {
|
createShadowRoot(host: Node, templateId: number): Node {
|
||||||
var sr = DOM.createShadowRoot(host);
|
var sr = DOM.createShadowRoot(host);
|
||||||
var styles = this._nativeShadowStyles.get(templateId);
|
var styles = this._nativeShadowStyles.get(templateId);
|
||||||
|
|
|
@ -25,7 +25,7 @@ export class DefaultRenderView<N> extends RenderViewRef {
|
||||||
|
|
||||||
constructor(public fragments: DefaultRenderFragmentRef<N>[], public boundTextNodes: N[],
|
constructor(public fragments: DefaultRenderFragmentRef<N>[], public boundTextNodes: N[],
|
||||||
public boundElements: N[], public nativeShadowRoots: N[],
|
public boundElements: N[], public nativeShadowRoots: N[],
|
||||||
public globalEventAdders: Function[]) {
|
public globalEventAdders: Function[], public rootContentInsertionPoints: N[]) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,8 @@ export function createRenderView(fragmentCmds: RenderTemplateCmd[], inplaceEleme
|
||||||
fragments.push(new DefaultRenderFragmentRef(context.fragments[i]));
|
fragments.push(new DefaultRenderFragmentRef(context.fragments[i]));
|
||||||
}
|
}
|
||||||
view = new DefaultRenderView<any>(fragments, context.boundTextNodes, context.boundElements,
|
view = new DefaultRenderView<any>(fragments, context.boundTextNodes, context.boundElements,
|
||||||
context.nativeShadowRoots, context.globalEventAdders);
|
context.nativeShadowRoots, context.globalEventAdders,
|
||||||
|
context.rootContentInsertionPoints);
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +32,7 @@ export interface NodeFactory<N> {
|
||||||
resolveComponentTemplate(templateId: number): RenderTemplateCmd[];
|
resolveComponentTemplate(templateId: number): RenderTemplateCmd[];
|
||||||
createTemplateAnchor(attrNameAndValues: string[]): N;
|
createTemplateAnchor(attrNameAndValues: string[]): N;
|
||||||
createElement(name: string, attrNameAndValues: string[]): N;
|
createElement(name: string, attrNameAndValues: string[]): N;
|
||||||
|
createRootContentInsertionPoint(): N;
|
||||||
mergeElement(existing: N, attrNameAndValues: string[]);
|
mergeElement(existing: N, attrNameAndValues: string[]);
|
||||||
createShadowRoot(host: N, templateId: number): N;
|
createShadowRoot(host: N, templateId: number): N;
|
||||||
createText(value: string): N;
|
createText(value: string): N;
|
||||||
|
@ -41,7 +43,9 @@ export interface NodeFactory<N> {
|
||||||
|
|
||||||
class BuildContext<N> {
|
class BuildContext<N> {
|
||||||
constructor(private _eventDispatcher: Function, public factory: NodeFactory<N>,
|
constructor(private _eventDispatcher: Function, public factory: NodeFactory<N>,
|
||||||
private _inplaceElement: N) {}
|
private _inplaceElement: N) {
|
||||||
|
this.isHost = isPresent((_inplaceElement));
|
||||||
|
}
|
||||||
private _builders: RenderViewBuilder<N>[] = [];
|
private _builders: RenderViewBuilder<N>[] = [];
|
||||||
|
|
||||||
globalEventAdders: Function[] = [];
|
globalEventAdders: Function[] = [];
|
||||||
|
@ -49,6 +53,9 @@ class BuildContext<N> {
|
||||||
boundTextNodes: N[] = [];
|
boundTextNodes: N[] = [];
|
||||||
nativeShadowRoots: N[] = [];
|
nativeShadowRoots: N[] = [];
|
||||||
fragments: N[][] = [];
|
fragments: N[][] = [];
|
||||||
|
rootContentInsertionPoints: N[] = [];
|
||||||
|
componentCount: number = 0;
|
||||||
|
isHost: boolean;
|
||||||
|
|
||||||
build(fragmentCmds: RenderTemplateCmd[]) {
|
build(fragmentCmds: RenderTemplateCmd[]) {
|
||||||
this.enqueueFragmentBuilder(null, fragmentCmds);
|
this.enqueueFragmentBuilder(null, fragmentCmds);
|
||||||
|
@ -65,6 +72,7 @@ class BuildContext<N> {
|
||||||
}
|
}
|
||||||
|
|
||||||
enqueueComponentBuilder(component: Component<N>) {
|
enqueueComponentBuilder(component: Component<N>) {
|
||||||
|
this.componentCount++;
|
||||||
this._builders.push(new RenderViewBuilder<N>(
|
this._builders.push(new RenderViewBuilder<N>(
|
||||||
component, null, this.factory.resolveComponentTemplate(component.cmd.templateId)));
|
component, null, this.factory.resolveComponentTemplate(component.cmd.templateId)));
|
||||||
}
|
}
|
||||||
|
@ -131,10 +139,20 @@ class RenderViewBuilder<N> implements RenderCommandVisitor {
|
||||||
}
|
}
|
||||||
visitNgContent(cmd: RenderNgContentCmd, context: BuildContext<N>): any {
|
visitNgContent(cmd: RenderNgContentCmd, context: BuildContext<N>): any {
|
||||||
if (isPresent(this.parentComponent)) {
|
if (isPresent(this.parentComponent)) {
|
||||||
var projectedNodes = this.parentComponent.project(cmd.index);
|
if (this.parentComponent.isRoot) {
|
||||||
for (var i = 0; i < projectedNodes.length; i++) {
|
var insertionPoint = context.factory.createRootContentInsertionPoint();
|
||||||
var node = projectedNodes[i];
|
if (this.parent instanceof Component) {
|
||||||
this._addChild(node, cmd.ngContentIndex, context);
|
context.factory.appendChild((<Component<N>>this.parent).shadowRoot, insertionPoint);
|
||||||
|
} else {
|
||||||
|
context.factory.appendChild(<N>this.parent, insertionPoint);
|
||||||
|
}
|
||||||
|
context.rootContentInsertionPoints.push(insertionPoint);
|
||||||
|
} else {
|
||||||
|
var projectedNodes = this.parentComponent.project(cmd.index);
|
||||||
|
for (var i = 0; i < projectedNodes.length; i++) {
|
||||||
|
var node = projectedNodes[i];
|
||||||
|
this._addChild(node, cmd.ngContentIndex, context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -154,7 +172,8 @@ class RenderViewBuilder<N> implements RenderCommandVisitor {
|
||||||
root = context.factory.createShadowRoot(el, cmd.templateId);
|
root = context.factory.createShadowRoot(el, cmd.templateId);
|
||||||
context.nativeShadowRoots.push(root);
|
context.nativeShadowRoots.push(root);
|
||||||
}
|
}
|
||||||
var component = new Component(el, root, cmd);
|
var isRoot = context.componentCount === 0 && context.isHost;
|
||||||
|
var component = new Component(el, root, cmd, isRoot);
|
||||||
context.enqueueComponentBuilder(component);
|
context.enqueueComponentBuilder(component);
|
||||||
this.parentStack.push(component);
|
this.parentStack.push(component);
|
||||||
return null;
|
return null;
|
||||||
|
@ -213,7 +232,8 @@ class RenderViewBuilder<N> implements RenderCommandVisitor {
|
||||||
class Component<N> {
|
class Component<N> {
|
||||||
private contentNodesByNgContentIndex: N[][] = [];
|
private contentNodesByNgContentIndex: N[][] = [];
|
||||||
|
|
||||||
constructor(public hostElement: N, public shadowRoot: N, public cmd: RenderBeginComponentCmd) {}
|
constructor(public hostElement: N, public shadowRoot: N, public cmd: RenderBeginComponentCmd,
|
||||||
|
public isRoot: boolean) {}
|
||||||
addContentNode(ngContentIndex: number, node: N, context: BuildContext<N>) {
|
addContentNode(ngContentIndex: number, node: N, context: BuildContext<N>) {
|
||||||
if (isBlank(ngContentIndex)) {
|
if (isBlank(ngContentIndex)) {
|
||||||
if (this.cmd.nativeShadow) {
|
if (this.cmd.nativeShadow) {
|
||||||
|
|
|
@ -517,6 +517,16 @@ export function main() {
|
||||||
expect(stringifyFragment(view.fragments[0].nodes))
|
expect(stringifyFragment(view.fragments[0].nodes))
|
||||||
.toEqual('<a-comp><b-comp>(hello)</b-comp></a-comp>');
|
.toEqual('<a-comp><b-comp>(hello)</b-comp></a-comp>');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should store content injection points for root component in a view', () => {
|
||||||
|
componentTemplates.set(0, [ngContent(0, null)]);
|
||||||
|
var view =
|
||||||
|
createRenderView([beginComponent('a-comp', [], [], false, null, 0), endComponent()],
|
||||||
|
DOM.createElement('root'), nodeFactory);
|
||||||
|
expect(stringifyFragment(view.rootContentInsertionPoints))
|
||||||
|
.toEqual('<root-content-insertion-point></root-content-insertion-point>');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -571,6 +581,9 @@ class DomNodeFactory implements NodeFactory<Node> {
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
createText(value: string): Node { return DOM.createTextNode(isPresent(value) ? value : ''); }
|
createText(value: string): Node { return DOM.createTextNode(isPresent(value) ? value : ''); }
|
||||||
|
createRootContentInsertionPoint(): Node {
|
||||||
|
return DOM.createElement('root-content-insertion-point');
|
||||||
|
}
|
||||||
appendChild(parent: Node, child: Node) { DOM.appendChild(parent, child); }
|
appendChild(parent: Node, child: Node) { DOM.appendChild(parent, child); }
|
||||||
on(element: Node, eventName: string, callback: Function) {
|
on(element: Node, eventName: string, callback: Function) {
|
||||||
this._localEventListeners.push(new LocalEventListener(element, eventName, callback));
|
this._localEventListeners.push(new LocalEventListener(element, eventName, callback));
|
||||||
|
|
|
@ -18,7 +18,7 @@ export function main() {
|
||||||
it('should register global event listeners', () => {
|
it('should register global event listeners', () => {
|
||||||
var addCount = 0;
|
var addCount = 0;
|
||||||
var adder = () => { addCount++ };
|
var adder = () => { addCount++ };
|
||||||
var view = new DefaultRenderView<Node>([], [], [], [], [adder]);
|
var view = new DefaultRenderView<Node>([], [], [], [], [adder], []);
|
||||||
view.hydrate();
|
view.hydrate();
|
||||||
expect(addCount).toBe(1);
|
expect(addCount).toBe(1);
|
||||||
});
|
});
|
||||||
|
@ -28,7 +28,7 @@ export function main() {
|
||||||
it('should deregister global event listeners', () => {
|
it('should deregister global event listeners', () => {
|
||||||
var removeCount = 0;
|
var removeCount = 0;
|
||||||
var adder = () => () => { removeCount++ };
|
var adder = () => () => { removeCount++ };
|
||||||
var view = new DefaultRenderView<Node>([], [], [], [], [adder]);
|
var view = new DefaultRenderView<Node>([], [], [], [], [adder], []);
|
||||||
view.hydrate();
|
view.hydrate();
|
||||||
view.dehydrate();
|
view.dehydrate();
|
||||||
expect(removeCount).toBe(1);
|
expect(removeCount).toBe(1);
|
||||||
|
|
|
@ -25,7 +25,7 @@ declare namespace angular {
|
||||||
require?: string;
|
require?: string;
|
||||||
restrict?: string;
|
restrict?: string;
|
||||||
scope?: {[key: string]: string};
|
scope?: {[key: string]: string};
|
||||||
link?: Function;
|
link?: {pre?: Function, post?: Function};
|
||||||
}
|
}
|
||||||
interface IAttributes {
|
interface IAttributes {
|
||||||
$observe(attr: string, fn: (v: string) => void);
|
$observe(attr: string, fn: (v: string) => void);
|
||||||
|
@ -33,6 +33,10 @@ declare namespace angular {
|
||||||
interface ITranscludeFunction {}
|
interface ITranscludeFunction {}
|
||||||
interface IAugmentedJQuery {
|
interface IAugmentedJQuery {
|
||||||
bind(name: string, fn: () => void);
|
bind(name: string, fn: () => void);
|
||||||
|
data(name: string, value?: any);
|
||||||
|
contents(): IAugmentedJQuery;
|
||||||
|
length: number;
|
||||||
|
[index: number]: Node;
|
||||||
}
|
}
|
||||||
interface IParseService {
|
interface IParseService {
|
||||||
(expression: string): ICompiledExpression;
|
(expression: string): ICompiledExpression;
|
||||||
|
@ -40,7 +44,7 @@ declare namespace angular {
|
||||||
interface ICompiledExpression {
|
interface ICompiledExpression {
|
||||||
assign(context: any, value: any): any;
|
assign(context: any, value: any): any;
|
||||||
}
|
}
|
||||||
function element(e: Element);
|
function element(e: Element): IAugmentedJQuery;
|
||||||
function bootstrap(e: Element, modules: IModule[], config: IAngularBootstrapConfig);
|
function bootstrap(e: Element, modules: IModule[], config: IAngularBootstrapConfig);
|
||||||
|
|
||||||
namespace auto {
|
namespace auto {
|
||||||
|
|
|
@ -5,10 +5,13 @@ import {
|
||||||
HostViewRef,
|
HostViewRef,
|
||||||
Injector,
|
Injector,
|
||||||
ProtoViewRef,
|
ProtoViewRef,
|
||||||
SimpleChange
|
SimpleChange,
|
||||||
|
ViewRef
|
||||||
} from 'angular2/angular2';
|
} from 'angular2/angular2';
|
||||||
import {NG1_SCOPE} from './constants';
|
import {NG1_SCOPE} from './constants';
|
||||||
import {ComponentInfo} from './metadata';
|
import {ComponentInfo} from './metadata';
|
||||||
|
import {ViewRef_} from "../../angular2/src/core/linker/view_ref";
|
||||||
|
import Element = protractor.Element;
|
||||||
|
|
||||||
const INITIAL_VALUE = {
|
const INITIAL_VALUE = {
|
||||||
__UNINITIALIZED__: true
|
__UNINITIALIZED__: true
|
||||||
|
@ -21,13 +24,17 @@ export class Ng2ComponentFacade {
|
||||||
hostViewRef: HostViewRef = null;
|
hostViewRef: HostViewRef = null;
|
||||||
changeDetector: ChangeDetectorRef = null;
|
changeDetector: ChangeDetectorRef = null;
|
||||||
componentScope: angular.IScope;
|
componentScope: angular.IScope;
|
||||||
|
childNodes: Node[];
|
||||||
|
contentInserctionPoint: Node = null;
|
||||||
|
|
||||||
constructor(private id: string, private info: ComponentInfo,
|
constructor(private id: string, private info: ComponentInfo,
|
||||||
private element: angular.IAugmentedJQuery, private attrs: angular.IAttributes,
|
private element: angular.IAugmentedJQuery, private attrs: angular.IAttributes,
|
||||||
private scope: angular.IScope, private parentInjector: Injector,
|
private scope: angular.IScope, private parentInjector: Injector,
|
||||||
private parse: angular.IParseService, private viewManager: AppViewManager,
|
private parse: angular.IParseService, private viewManager: AppViewManager,
|
||||||
private protoView: ProtoViewRef) {
|
private protoView: ProtoViewRef) {
|
||||||
|
(<any>this.element[0]).id = id;
|
||||||
this.componentScope = scope.$new();
|
this.componentScope = scope.$new();
|
||||||
|
this.childNodes = <Node[]><any>element.contents();
|
||||||
}
|
}
|
||||||
|
|
||||||
bootstrapNg2() {
|
bootstrapNg2() {
|
||||||
|
@ -35,9 +42,11 @@ export class Ng2ComponentFacade {
|
||||||
this.parentInjector.resolveAndCreateChild([bind(NG1_SCOPE).toValue(this.componentScope)]);
|
this.parentInjector.resolveAndCreateChild([bind(NG1_SCOPE).toValue(this.componentScope)]);
|
||||||
this.hostViewRef =
|
this.hostViewRef =
|
||||||
this.viewManager.createRootHostView(this.protoView, '#' + this.id, childInjector);
|
this.viewManager.createRootHostView(this.protoView, '#' + this.id, childInjector);
|
||||||
|
var renderer: any = (<any>this.hostViewRef).render;
|
||||||
var hostElement = this.viewManager.getHostElement(this.hostViewRef);
|
var hostElement = this.viewManager.getHostElement(this.hostViewRef);
|
||||||
this.changeDetector = this.hostViewRef.changeDetectorRef;
|
this.changeDetector = this.hostViewRef.changeDetectorRef;
|
||||||
this.component = this.viewManager.getComponent(hostElement);
|
this.component = this.viewManager.getComponent(hostElement);
|
||||||
|
this.contentInserctionPoint = renderer.rootContentInsertionPoints[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
setupInputs() {
|
setupInputs() {
|
||||||
|
@ -94,6 +103,16 @@ export class Ng2ComponentFacade {
|
||||||
this.componentScope.$watch(() => this.changeDetector.detectChanges());
|
this.componentScope.$watch(() => this.changeDetector.detectChanges());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
projectContent() {
|
||||||
|
var childNodes = this.childNodes;
|
||||||
|
if (this.contentInserctionPoint) {
|
||||||
|
var parent = this.contentInserctionPoint.parentNode;
|
||||||
|
for (var i = 0, ii = childNodes.length; i < ii; i++) {
|
||||||
|
parent.insertBefore(childNodes[i], this.contentInserctionPoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setupOutputs() {
|
setupOutputs() {
|
||||||
var attrs = this.attrs;
|
var attrs = this.attrs;
|
||||||
var outputs = this.info.outputs;
|
var outputs = this.info.outputs;
|
||||||
|
|
|
@ -169,16 +169,19 @@ function ng1ComponentDirective(info: ComponentInfo, idPrefix: string): Function
|
||||||
return {
|
return {
|
||||||
restrict: 'E',
|
restrict: 'E',
|
||||||
require: REQUIRE_INJECTOR,
|
require: REQUIRE_INJECTOR,
|
||||||
link: (scope: angular.IScope, element: angular.IAugmentedJQuery, attrs: angular.IAttributes,
|
link: {
|
||||||
parentInjector: any, transclude: angular.ITranscludeFunction): void => {
|
post: (scope: angular.IScope, element: angular.IAugmentedJQuery, attrs: angular.IAttributes,
|
||||||
var facade =
|
parentInjector: any, transclude: angular.ITranscludeFunction): void => {
|
||||||
new Ng2ComponentFacade(element[0].id = idPrefix + (idCount++), info, element, attrs,
|
var domElement = <any>element[0];
|
||||||
scope, <Injector>parentInjector, parse, viewManager, protoView);
|
var facade =
|
||||||
|
new Ng2ComponentFacade(idPrefix + (idCount++), info, element, attrs, scope,
|
||||||
facade.setupInputs();
|
<Injector>parentInjector, parse, viewManager, protoView);
|
||||||
facade.bootstrapNg2();
|
facade.setupInputs();
|
||||||
facade.setupOutputs();
|
facade.bootstrapNg2();
|
||||||
facade.registerCleanup();
|
facade.projectContent();
|
||||||
|
facade.setupOutputs();
|
||||||
|
facade.registerCleanup();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,17 +18,18 @@ export function main() {
|
||||||
describe('upgrade: ng1 to ng2', () => {
|
describe('upgrade: ng1 to ng2', () => {
|
||||||
it('should have angular 1 loaded', () => expect(angular.version.major).toBe(1));
|
it('should have angular 1 loaded', () => expect(angular.version.major).toBe(1));
|
||||||
|
|
||||||
it('should instantiate ng2 in ng1 template', inject([AsyncTestCompleter], (async) => {
|
it('should instantiate ng2 in ng1 template and project content',
|
||||||
|
inject([AsyncTestCompleter], (async) => {
|
||||||
var Ng2 = Component({selector: 'ng2'})
|
var Ng2 = Component({selector: 'ng2'})
|
||||||
.View({template: `{{ 'NG2' }}`})
|
.View({template: `{{ 'NG2' }}(<ng-content></ng-content>)`})
|
||||||
.Class({constructor: function() {}});
|
.Class({constructor: function() {}});
|
||||||
|
|
||||||
var element = html("<div>{{ 'ng1-' }}<ng2>~~</ng2>{{ '-ng1' }}</div>");
|
var element = html("<div>{{ 'ng1[' }}<ng2>~{{ 'ng-content' }}~</ng2>{{ ']' }}</div>");
|
||||||
|
|
||||||
var upgradeModule: UpgradeModule = createUpgradeModule();
|
var upgradeModule: UpgradeModule = createUpgradeModule();
|
||||||
upgradeModule.importNg2Component(Ng2);
|
upgradeModule.importNg2Component(Ng2);
|
||||||
upgradeModule.bootstrap(element).ready(() => {
|
upgradeModule.bootstrap(element).ready(() => {
|
||||||
expect(document.body.textContent).toEqual("ng1-NG2-ng1");
|
expect(document.body.textContent).toEqual("ng1[NG2(~ng-content~)]");
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
Loading…
Reference in New Issue