refactor(compiler): speed up proto view merging

- Don't create intermediate merge results
- Only merge embedded ProtoViews that contain `<ng-content>` tags

Closes #3150
Closes #3177
This commit is contained in:
Tobias Bosch 2015-07-20 09:59:44 -07:00
parent de18da2a0d
commit 078475a082
20 changed files with 359 additions and 466 deletions

View File

@ -24,6 +24,7 @@ import {ComponentUrlMapper} from './component_url_mapper';
import {ProtoViewFactory} from './proto_view_factory'; import {ProtoViewFactory} from './proto_view_factory';
import {UrlResolver} from 'angular2/src/services/url_resolver'; import {UrlResolver} from 'angular2/src/services/url_resolver';
import {AppRootUrl} from 'angular2/src/services/app_root_url'; import {AppRootUrl} from 'angular2/src/services/app_root_url';
import {ElementBinder} from './element_binder';
import * as renderApi from 'angular2/src/render/api'; import * as renderApi from 'angular2/src/render/api';
@ -90,7 +91,7 @@ export class Compiler {
private _appUrl: string; private _appUrl: string;
private _render: renderApi.RenderCompiler; private _render: renderApi.RenderCompiler;
private _protoViewFactory: ProtoViewFactory; private _protoViewFactory: ProtoViewFactory;
private _unmergedCyclicEmbeddedProtoViews: RecursiveEmbeddedProtoView[] = []; private _protoViewsToBeMerged: AppProtoView[] = [];
/** /**
* @private * @private
@ -146,8 +147,37 @@ export class Compiler {
return this._compileNestedProtoViews(hostRenderPv, protoView, componentType); return this._compileNestedProtoViews(hostRenderPv, protoView, componentType);
}); });
} }
return hostPvPromise.then( return hostPvPromise.then(hostAppProtoView =>
hostAppProtoView => this._mergeCyclicEmbeddedProtoViews().then(_ => hostAppProtoView.ref)); this._mergeUnmergedProtoViews().then(_ => hostAppProtoView.ref));
}
private _mergeUnmergedProtoViews(): Promise<any> {
var protoViewsToBeMerged = this._protoViewsToBeMerged;
this._protoViewsToBeMerged = [];
return PromiseWrapper.all(protoViewsToBeMerged.map((appProtoView) => {
return this._render.mergeProtoViewsRecursively(
this._collectMergeRenderProtoViews(appProtoView))
.then((mergeResult: renderApi.RenderProtoViewMergeMapping) => {
appProtoView.mergeMapping = new AppProtoViewMergeMapping(mergeResult);
});
}));
}
private _collectMergeRenderProtoViews(
appProtoView: AppProtoView): List<renderApi.RenderProtoViewRef | List<any>> {
var result = [appProtoView.render];
for (var i = 0; i < appProtoView.elementBinders.length; i++) {
var binder = appProtoView.elementBinders[i];
if (isPresent(binder.nestedProtoView)) {
if (binder.hasStaticComponent() ||
(binder.hasEmbeddedProtoView() && binder.nestedProtoView.isEmbeddedFragment)) {
result.push(this._collectMergeRenderProtoViews(binder.nestedProtoView));
} else {
result.push(null);
}
}
}
return result;
} }
private _compile(componentBinding: DirectiveBinding): Promise<AppProtoView>| AppProtoView { private _compile(componentBinding: DirectiveBinding): Promise<AppProtoView>| AppProtoView {
@ -207,7 +237,7 @@ export class Compiler {
appProtoView: AppProtoView, appProtoView: AppProtoView,
componentType: Type): Promise<AppProtoView> { componentType: Type): Promise<AppProtoView> {
var nestedPVPromises = []; var nestedPVPromises = [];
this._loopComponentElementBinders(appProtoView, (parentPv, elementBinder) => { this._loopComponentElementBinders(appProtoView, (parentPv, elementBinder: ElementBinder) => {
var nestedComponent = elementBinder.componentDirective; var nestedComponent = elementBinder.componentDirective;
var elementBinderDone = var elementBinderDone =
(nestedPv: AppProtoView) => { elementBinder.nestedProtoView = nestedPv; }; (nestedPv: AppProtoView) => { elementBinder.nestedProtoView = nestedPv; };
@ -220,31 +250,40 @@ export class Compiler {
}); });
return PromiseWrapper.all(nestedPVPromises) return PromiseWrapper.all(nestedPVPromises)
.then((_) => { .then((_) => {
var appProtoViewsToMergeInto = []; this._collectMergableProtoViews(appProtoView, componentType);
var mergeRenderProtoViews = this._collectMergeRenderProtoViewsRecurse( return appProtoView;
renderProtoView, appProtoView, appProtoViewsToMergeInto);
if (isBlank(mergeRenderProtoViews)) {
throw new BaseException(`Unconditional component cycle in ${stringify(componentType)}`);
}
return this._mergeProtoViews(appProtoViewsToMergeInto, mergeRenderProtoViews);
}); });
} }
private _mergeProtoViews( private _collectMergableProtoViews(appProtoView: AppProtoView, componentType: Type) {
appProtoViewsToMergeInto: AppProtoView[], var isRecursive = false;
mergeRenderProtoViews: for (var i = 0; i < appProtoView.elementBinders.length; i++) {
List<renderApi.RenderProtoViewRef | List<any>>): Promise<AppProtoView> { var binder = appProtoView.elementBinders[i];
return this._render.mergeProtoViewsRecursively(mergeRenderProtoViews) if (binder.hasStaticComponent()) {
.then((mergeResults: List<renderApi.RenderProtoViewMergeMapping>) => { if (isBlank(binder.nestedProtoView.isRecursive)) {
// Note: We don't need to check for nulls here as we filtered them out before! // cycle via a component. We are in the tail recursion,
// (in RenderCompiler.mergeProtoViewsRecursively and // so all components should have their isRecursive flag set already.
// _collectMergeRenderProtoViewsRecurse). isRecursive = true;
for (var i = 0; i < mergeResults.length; i++) { break;
appProtoViewsToMergeInto[i].mergeMapping =
new AppProtoViewMergeMapping(mergeResults[i]);
} }
return appProtoViewsToMergeInto[0]; } else if (binder.hasEmbeddedProtoView()) {
}); this._collectMergableProtoViews(binder.nestedProtoView, componentType);
}
}
if (isRecursive) {
if (appProtoView.isEmbeddedFragment) {
throw new BaseException(
`<ng-content> is used within the recursive path of ${stringify(componentType)}`);
}
if (appProtoView.type === renderApi.ViewType.COMPONENT) {
throw new BaseException(`Unconditional component cycle in ${stringify(componentType)}`);
}
}
if (appProtoView.type === renderApi.ViewType.EMBEDDED ||
appProtoView.type === renderApi.ViewType.HOST) {
this._protoViewsToBeMerged.push(appProtoView);
}
appProtoView.isRecursive = isRecursive;
} }
private _loopComponentElementBinders(appProtoView: AppProtoView, callback: Function) { private _loopComponentElementBinders(appProtoView: AppProtoView, callback: Function) {
@ -257,48 +296,6 @@ export class Compiler {
}); });
} }
private _collectMergeRenderProtoViewsRecurse(
renderProtoView: renderApi.ProtoViewDto, appProtoView: AppProtoView,
targetAppProtoViews: AppProtoView[]): List<renderApi.RenderProtoViewRef | List<any>> {
targetAppProtoViews.push(appProtoView);
var result = [renderProtoView.render];
for (var i = 0; i < appProtoView.elementBinders.length; i++) {
var binder = appProtoView.elementBinders[i];
if (binder.hasStaticComponent()) {
if (isBlank(binder.nestedProtoView.mergeMapping)) {
// cycle via an embedded ProtoView. store the AppProtoView and ProtoViewDto for later.
this._unmergedCyclicEmbeddedProtoViews.push(
new RecursiveEmbeddedProtoView(appProtoView, renderProtoView));
return null;
}
result.push(binder.nestedProtoView.mergeMapping.renderProtoViewRef);
} else if (binder.hasEmbeddedProtoView()) {
result.push(this._collectMergeRenderProtoViewsRecurse(
renderProtoView.elementBinders[i].nestedProtoView, binder.nestedProtoView,
targetAppProtoViews));
}
}
return result;
}
private _mergeCyclicEmbeddedProtoViews() {
var pvs = this._unmergedCyclicEmbeddedProtoViews;
this._unmergedCyclicEmbeddedProtoViews = [];
var promises = pvs.map(entry => {
var appProtoView = entry.appProtoView;
var mergeRenderProtoViews = [entry.renderProtoView.render];
appProtoView.elementBinders.forEach((binder) => {
if (binder.hasStaticComponent()) {
mergeRenderProtoViews.push(binder.nestedProtoView.mergeMapping.renderProtoViewRef);
} else if (binder.hasEmbeddedProtoView()) {
mergeRenderProtoViews.push(null);
}
});
return this._mergeProtoViews([appProtoView], mergeRenderProtoViews);
});
return PromiseWrapper.all(promises);
}
private _buildRenderTemplate(component, view, directives): renderApi.ViewDefinition { private _buildRenderTemplate(component, view, directives): renderApi.ViewDefinition {
var componentUrl = var componentUrl =
this._urlResolver.resolve(this._appUrl, this._componentUrlMapper.getUrl(component)); this._urlResolver.resolve(this._appUrl, this._componentUrlMapper.getUrl(component));
@ -356,7 +353,3 @@ export class Compiler {
} }
} }
} }
class RecursiveEmbeddedProtoView {
constructor(public appProtoView: AppProtoView, public renderProtoView: renderApi.ProtoViewDto) {}
}

View File

@ -254,8 +254,11 @@ function _createAppProtoView(
renderProtoView: renderApi.ProtoViewDto, protoChangeDetector: ProtoChangeDetector, renderProtoView: renderApi.ProtoViewDto, protoChangeDetector: ProtoChangeDetector,
variableBindings: Map<string, string>, allDirectives: List<DirectiveBinding>): AppProtoView { variableBindings: Map<string, string>, allDirectives: List<DirectiveBinding>): AppProtoView {
var elementBinders = renderProtoView.elementBinders; var elementBinders = renderProtoView.elementBinders;
var protoView = new AppProtoView(renderProtoView.type, protoChangeDetector, variableBindings, // Embedded ProtoViews that contain `<ng-content>` will be merged into their parents and use
createVariableLocations(elementBinders), // a RenderFragmentRef. I.e. renderProtoView.transitiveNgContentCount > 0.
var protoView = new AppProtoView(
renderProtoView.type, renderProtoView.transitiveNgContentCount > 0, renderProtoView.render,
protoChangeDetector, variableBindings, createVariableLocations(elementBinders),
renderProtoView.textBindings.length); renderProtoView.textBindings.length);
_createElementBinders(protoView, elementBinders, allDirectives); _createElementBinders(protoView, elementBinders, allDirectives);
_bindDirectiveEvents(protoView, elementBinders); _bindDirectiveEvents(protoView, elementBinders);

View File

@ -32,6 +32,7 @@ export class AppProtoViewMergeMapping {
renderTextIndices: number[]; renderTextIndices: number[];
nestedViewIndicesByElementIndex: number[]; nestedViewIndicesByElementIndex: number[];
hostElementIndicesByViewIndex: number[]; hostElementIndicesByViewIndex: number[];
nestedViewCountByViewIndex: number[];
constructor(renderProtoViewMergeMapping: renderApi.RenderProtoViewMergeMapping) { constructor(renderProtoViewMergeMapping: renderApi.RenderProtoViewMergeMapping) {
this.renderProtoViewRef = renderProtoViewMergeMapping.mergedProtoViewRef; this.renderProtoViewRef = renderProtoViewMergeMapping.mergedProtoViewRef;
this.renderFragmentCount = renderProtoViewMergeMapping.fragmentCount; this.renderFragmentCount = renderProtoViewMergeMapping.fragmentCount;
@ -42,11 +43,8 @@ export class AppProtoViewMergeMapping {
this.hostElementIndicesByViewIndex = renderProtoViewMergeMapping.hostElementIndicesByViewIndex; this.hostElementIndicesByViewIndex = renderProtoViewMergeMapping.hostElementIndicesByViewIndex;
this.nestedViewIndicesByElementIndex = this.nestedViewIndicesByElementIndex =
inverseIndexMapping(this.hostElementIndicesByViewIndex, this.renderElementIndices.length); inverseIndexMapping(this.hostElementIndicesByViewIndex, this.renderElementIndices.length);
this.nestedViewCountByViewIndex = renderProtoViewMergeMapping.nestedViewCountByViewIndex;
} }
get viewCount() { return this.hostElementIndicesByViewIndex.length; }
get elementCount() { return this.renderElementIndices.length; }
} }
function inverseIndexMapping(input: number[], resultLength: number): number[] { function inverseIndexMapping(input: number[], resultLength: number): number[] {
@ -215,7 +213,7 @@ export class AppView implements ChangeDispatcher, RenderEventDispatcher {
dispatchRenderEvent(renderElementIndex: number, eventName: string, dispatchRenderEvent(renderElementIndex: number, eventName: string,
locals: Map<string, any>): boolean { locals: Map<string, any>): boolean {
var elementRef = var elementRef =
this.elementRefs[this.proto.mergeMapping.renderInverseElementIndices[renderElementIndex]]; this.elementRefs[this.mainMergeMapping.renderInverseElementIndices[renderElementIndex]];
var view = internalView(elementRef.parentView); var view = internalView(elementRef.parentView);
return view.dispatchEvent(elementRef.boundElementIndex, eventName, locals); return view.dispatchEvent(elementRef.boundElementIndex, eventName, locals);
} }
@ -258,7 +256,11 @@ export class AppProtoView {
mergeMapping: AppProtoViewMergeMapping; mergeMapping: AppProtoViewMergeMapping;
ref: ProtoViewRef; ref: ProtoViewRef;
constructor(public type: renderApi.ViewType, public protoChangeDetector: ProtoChangeDetector, isRecursive: boolean = null;
constructor(public type: renderApi.ViewType, public isEmbeddedFragment: boolean,
public render: renderApi.RenderProtoViewRef,
public protoChangeDetector: ProtoChangeDetector,
public variableBindings: Map<string, string>, public variableBindings: Map<string, string>,
public variableLocations: Map<string, number>, public textBindingCount: number) { public variableLocations: Map<string, number>, public textBindingCount: number) {
this.ref = new ProtoViewRef(this); this.ref = new ProtoViewRef(this);

View File

@ -339,12 +339,19 @@ export class AppViewManager {
this._utils.dehydrateView(view); this._utils.dehydrateView(view);
} }
var viewContainers = view.viewContainers; var viewContainers = view.viewContainers;
for (var i = view.elementOffset, ii = view.elementOffset + view.proto.mergeMapping.elementCount; var startViewOffset = view.viewOffset;
i < ii; i++) { var endViewOffset =
var vc = viewContainers[i]; view.viewOffset + view.mainMergeMapping.nestedViewCountByViewIndex[view.viewOffset];
var elementOffset = view.elementOffset;
for (var viewIdx = startViewOffset; viewIdx <= endViewOffset; viewIdx++) {
var currView = view.views[viewIdx];
for (var binderIdx = 0; binderIdx < currView.proto.elementBinders.length;
binderIdx++, elementOffset++) {
var vc = viewContainers[elementOffset];
if (isPresent(vc)) { if (isPresent(vc)) {
for (var j = vc.views.length - 1; j >= 0; j--) { for (var j = vc.views.length - 1; j >= 0; j--) {
this._destroyViewInContainer(view, i, j); this._destroyViewInContainer(currView, elementOffset, j);
}
} }
} }
} }

View File

@ -26,8 +26,8 @@ export class AppViewManagerUtils {
var renderFragments = renderViewWithFragments.fragmentRefs; var renderFragments = renderViewWithFragments.fragmentRefs;
var renderView = renderViewWithFragments.viewRef; var renderView = renderViewWithFragments.viewRef;
var elementCount = mergedParentViewProto.mergeMapping.elementCount; var elementCount = mergedParentViewProto.mergeMapping.renderElementIndices.length;
var viewCount = mergedParentViewProto.mergeMapping.viewCount; var viewCount = mergedParentViewProto.mergeMapping.nestedViewCountByViewIndex[0] + 1;
var elementRefs: ElementRef[] = ListWrapper.createFixedSize(elementCount); var elementRefs: ElementRef[] = ListWrapper.createFixedSize(elementCount);
var viewContainers = ListWrapper.createFixedSize(elementCount); var viewContainers = ListWrapper.createFixedSize(elementCount);
var preBuiltObjects: eli.PreBuiltObjects[] = ListWrapper.createFixedSize(elementCount); var preBuiltObjects: eli.PreBuiltObjects[] = ListWrapper.createFixedSize(elementCount);
@ -175,13 +175,13 @@ export class AppViewManagerUtils {
_hydrateView(initView: viewModule.AppView, imperativelyCreatedInjector: Injector, _hydrateView(initView: viewModule.AppView, imperativelyCreatedInjector: Injector,
hostElementInjector: eli.ElementInjector, context: Object, parentLocals: Locals) { hostElementInjector: eli.ElementInjector, context: Object, parentLocals: Locals) {
var viewIdx = initView.viewOffset; var viewIdx = initView.viewOffset;
var endViewOffset = viewIdx + initView.proto.mergeMapping.viewCount; var endViewOffset = viewIdx + initView.mainMergeMapping.nestedViewCountByViewIndex[viewIdx];
while (viewIdx < endViewOffset) { while (viewIdx <= endViewOffset) {
var currView = initView.views[viewIdx]; var currView = initView.views[viewIdx];
var currProtoView = currView.proto; var currProtoView = currView.proto;
if (currView !== initView && currView.proto.type === ViewType.EMBEDDED) { if (currView !== initView && currView.proto.type === ViewType.EMBEDDED) {
// Don't hydrate components of embedded fragment views. // Don't hydrate components of embedded fragment views.
viewIdx += currProtoView.mergeMapping.viewCount; viewIdx += initView.mainMergeMapping.nestedViewCountByViewIndex[viewIdx] + 1;
} else { } else {
if (currView !== initView) { if (currView !== initView) {
// hydrate a nested component view // hydrate a nested component view
@ -263,9 +263,9 @@ export class AppViewManagerUtils {
} }
dehydrateView(initView: viewModule.AppView) { dehydrateView(initView: viewModule.AppView) {
for (var viewIdx = initView.viewOffset, var endViewOffset = initView.viewOffset +
endViewOffset = viewIdx + initView.proto.mergeMapping.viewCount; initView.mainMergeMapping.nestedViewCountByViewIndex[initView.viewOffset];
viewIdx < endViewOffset; viewIdx++) { for (var viewIdx = initView.viewOffset; viewIdx <= endViewOffset; viewIdx++) {
var currView = initView.views[viewIdx]; var currView = initView.views[viewIdx];
if (currView.hydrated()) { if (currView.hydrated()) {
if (isPresent(currView.locals)) { if (isPresent(currView.locals)) {

View File

@ -114,19 +114,23 @@ export class ProtoViewDto {
variableBindings: Map<string, string>; variableBindings: Map<string, string>;
type: ViewType; type: ViewType;
textBindings: List<ASTWithSource>; textBindings: List<ASTWithSource>;
transitiveNgContentCount: number;
constructor({render, elementBinders, variableBindings, type, textBindings}: { constructor({render, elementBinders, variableBindings, type, textBindings,
transitiveNgContentCount}: {
render?: RenderProtoViewRef, render?: RenderProtoViewRef,
elementBinders?: List<ElementBinder>, elementBinders?: List<ElementBinder>,
variableBindings?: Map<string, string>, variableBindings?: Map<string, string>,
type?: ViewType, type?: ViewType,
textBindings?: List<ASTWithSource> textBindings?: List<ASTWithSource>,
transitiveNgContentCount?: number
}) { }) {
this.render = render; this.render = render;
this.elementBinders = elementBinders; this.elementBinders = elementBinders;
this.variableBindings = variableBindings; this.variableBindings = variableBindings;
this.type = type; this.type = type;
this.textBindings = textBindings; this.textBindings = textBindings;
this.transitiveNgContentCount = transitiveNgContentCount;
} }
} }
@ -308,7 +312,9 @@ export class RenderProtoViewMergeMapping {
// indices for one ProtoView in a consecuitve block. // indices for one ProtoView in a consecuitve block.
public mappedTextIndices: number[], public mappedTextIndices: number[],
// Mapping from view index to app element index // Mapping from view index to app element index
public hostElementIndicesByViewIndex: number[]) {} public hostElementIndicesByViewIndex: number[],
// Number of contained views by view index
public nestedViewCountByViewIndex: number[]) {}
} }
export class RenderCompiler { export class RenderCompiler {
@ -331,10 +337,10 @@ export class RenderCompiler {
* If the array contains other arrays, they will be merged before processing the parent array. * If the array contains other arrays, they will be merged before processing the parent array.
* The array must contain an entry for every component and embedded ProtoView of the first entry. * The array must contain an entry for every component and embedded ProtoView of the first entry.
* @param protoViewRefs List of ProtoViewRefs or nested * @param protoViewRefs List of ProtoViewRefs or nested
* @return the merge result for every input array in depth first order. * @return the merge result
*/ */
mergeProtoViewsRecursively( mergeProtoViewsRecursively(
protoViewRefs: List<RenderProtoViewRef | List<any>>): Promise<RenderProtoViewMergeMapping[]> { protoViewRefs: List<RenderProtoViewRef | List<any>>): Promise<RenderProtoViewMergeMapping> {
return null; return null;
} }
} }

View File

@ -54,8 +54,7 @@ export class DomCompiler extends RenderCompiler {
} }
mergeProtoViewsRecursively( mergeProtoViewsRecursively(
protoViewRefs: protoViewRefs: List<RenderProtoViewRef | List<any>>): Promise<RenderProtoViewMergeMapping> {
List<RenderProtoViewRef | List<any>>): Promise<List<RenderProtoViewMergeMapping>> {
return PromiseWrapper.resolve(pvm.mergeProtoViewsRecursively(protoViewRefs)); return PromiseWrapper.resolve(pvm.mergeProtoViewsRecursively(protoViewRefs));
} }

View File

@ -1,17 +1,17 @@
import {DOM} from 'angular2/src/dom/dom_adapter';
import {CompileStep} from '../compiler/compile_step'; import {CompileStep} from '../compiler/compile_step';
import {CompileElement} from '../compiler/compile_element'; import {CompileElement} from '../compiler/compile_element';
import {CompileControl} from '../compiler/compile_control'; import {CompileControl} from '../compiler/compile_control';
import {ViewDefinition} from '../../api'; import {ViewDefinition} from '../../api';
import {ShadowDomStrategy} from './shadow_dom_strategy'; import {ShadowDomStrategy} from './shadow_dom_strategy';
import {NG_CONTENT_ELEMENT_NAME, isElementWithTag} from '../util';
export class ShadowDomCompileStep implements CompileStep { export class ShadowDomCompileStep implements CompileStep {
constructor(public _shadowDomStrategy: ShadowDomStrategy, public _view: ViewDefinition) {} constructor(public _shadowDomStrategy: ShadowDomStrategy, public _view: ViewDefinition) {}
process(parent: CompileElement, current: CompileElement, control: CompileControl) { process(parent: CompileElement, current: CompileElement, control: CompileControl) {
var tagName = DOM.tagName(current.element).toUpperCase(); if (isElementWithTag(current.element, NG_CONTENT_ELEMENT_NAME)) {
if (tagName == 'STYLE') { current.inheritedProtoView.bindNgContent();
} else if (isElementWithTag(current.element, 'style')) {
this._processStyleElement(current, control); this._processStyleElement(current, control);
} else { } else {
var componentId = current.isBound() ? current.inheritedElementBinder.componentId : null; var componentId = current.isBound() ? current.inheritedElementBinder.componentId : null;

View File

@ -1,4 +1,3 @@
import {isBlank} from 'angular2/src/facade/lang';
import {List, ListWrapper} from 'angular2/src/facade/collection'; import {List, ListWrapper} from 'angular2/src/facade/collection';
import {DomElementBinder} from './element_binder'; import {DomElementBinder} from './element_binder';
@ -16,39 +15,21 @@ export class DomProtoViewRef extends RenderProtoViewRef {
export class DomProtoView { export class DomProtoView {
static create(type: ViewType, rootElement: Element, fragmentsRootNodeCount: number[], static create(type: ViewType, rootElement: Element, fragmentsRootNodeCount: number[],
rootTextNodeIndices: number[], elementBinders: List<DomElementBinder>, rootTextNodeIndices: number[],
mappedElementIndices: number[], mappedTextIndices: number[], elementBinders: List<DomElementBinder>): DomProtoView {
hostElementIndicesByViewIndex: number[]): DomProtoView {
var boundTextNodeCount = rootTextNodeIndices.length; var boundTextNodeCount = rootTextNodeIndices.length;
for (var i = 0; i < elementBinders.length; i++) { for (var i = 0; i < elementBinders.length; i++) {
boundTextNodeCount += elementBinders[i].textNodeIndices.length; boundTextNodeCount += elementBinders[i].textNodeIndices.length;
} }
if (isBlank(mappedElementIndices)) {
mappedElementIndices = ListWrapper.createFixedSize(elementBinders.length);
for (var i = 0; i < mappedElementIndices.length; i++) {
mappedElementIndices[i] = i;
}
}
if (isBlank(mappedTextIndices)) {
mappedTextIndices = ListWrapper.createFixedSize(boundTextNodeCount);
for (var i = 0; i < mappedTextIndices.length; i++) {
mappedTextIndices[i] = i;
}
}
if (isBlank(hostElementIndicesByViewIndex)) {
hostElementIndicesByViewIndex = [null];
}
var isSingleElementFragment = fragmentsRootNodeCount.length === 1 && var isSingleElementFragment = fragmentsRootNodeCount.length === 1 &&
fragmentsRootNodeCount[0] === 1 && fragmentsRootNodeCount[0] === 1 &&
DOM.isElementNode(DOM.firstChild(DOM.content(rootElement))); DOM.isElementNode(DOM.firstChild(DOM.content(rootElement)));
return new DomProtoView(type, rootElement, elementBinders, rootTextNodeIndices, return new DomProtoView(type, rootElement, elementBinders, rootTextNodeIndices,
boundTextNodeCount, fragmentsRootNodeCount, isSingleElementFragment, boundTextNodeCount, fragmentsRootNodeCount, isSingleElementFragment);
mappedElementIndices, mappedTextIndices, hostElementIndicesByViewIndex);
} }
constructor(public type: ViewType, public rootElement: Element, constructor(public type: ViewType, public rootElement: Element,
public elementBinders: List<DomElementBinder>, public rootTextNodeIndices: number[], public elementBinders: List<DomElementBinder>, public rootTextNodeIndices: number[],
public boundTextNodeCount: number, public fragmentsRootNodeCount: number[], public boundTextNodeCount: number, public fragmentsRootNodeCount: number[],
public isSingleElementFragment: boolean, public mappedElementIndices: number[], public isSingleElementFragment: boolean) {}
public mappedTextIndices: number[], public hostElementIndicesByViewIndex: number[]) {}
} }

View File

@ -29,6 +29,7 @@ export class ProtoViewBuilder {
variableBindings: Map<string, string> = new Map(); variableBindings: Map<string, string> = new Map();
elements: List<ElementBinderBuilder> = []; elements: List<ElementBinderBuilder> = [];
rootTextBindings: Map<Node, ASTWithSource> = new Map(); rootTextBindings: Map<Node, ASTWithSource> = new Map();
ngContentCount: number = 0;
constructor(public rootElement, public type: api.ViewType, constructor(public rootElement, public type: api.ViewType,
public useNativeShadowDom: boolean = false) {} public useNativeShadowDom: boolean = false) {}
@ -57,12 +58,15 @@ export class ProtoViewBuilder {
this.rootTextBindings.set(textNode, expression); this.rootTextBindings.set(textNode, expression);
} }
bindNgContent() { this.ngContentCount++; }
build(): api.ProtoViewDto { build(): api.ProtoViewDto {
var domElementBinders = []; var domElementBinders = [];
var apiElementBinders = []; var apiElementBinders = [];
var textNodeExpressions = []; var textNodeExpressions = [];
var rootTextNodeIndices = []; var rootTextNodeIndices = [];
var transitiveNgContentCount = this.ngContentCount;
queryBoundTextNodeIndices(DOM.content(this.rootElement), this.rootTextBindings, queryBoundTextNodeIndices(DOM.content(this.rootElement), this.rootTextBindings,
(node, nodeIndex, expression) => { (node, nodeIndex, expression) => {
textNodeExpressions.push(expression); textNodeExpressions.push(expression);
@ -85,6 +89,9 @@ export class ProtoViewBuilder {
}); });
}); });
var nestedProtoView = isPresent(ebb.nestedProtoView) ? ebb.nestedProtoView.build() : null; var nestedProtoView = isPresent(ebb.nestedProtoView) ? ebb.nestedProtoView.build() : null;
if (isPresent(nestedProtoView)) {
transitiveNgContentCount += nestedProtoView.transitiveNgContentCount;
}
var parentIndex = isPresent(ebb.parent) ? ebb.parent.index : -1; var parentIndex = isPresent(ebb.parent) ? ebb.parent.index : -1;
var textNodeIndices = []; var textNodeIndices = [];
queryBoundTextNodeIndices(ebb.element, ebb.textBindings, (node, nodeIndex, expression) => { queryBoundTextNodeIndices(ebb.element, ebb.textBindings, (node, nodeIndex, expression) => {
@ -116,12 +123,12 @@ export class ProtoViewBuilder {
var rootNodeCount = DOM.childNodes(DOM.content(this.rootElement)).length; var rootNodeCount = DOM.childNodes(DOM.content(this.rootElement)).length;
return new api.ProtoViewDto({ return new api.ProtoViewDto({
render: new DomProtoViewRef(DomProtoView.create(this.type, this.rootElement, [rootNodeCount], render: new DomProtoViewRef(DomProtoView.create(this.type, this.rootElement, [rootNodeCount],
rootTextNodeIndices, domElementBinders, null, rootTextNodeIndices, domElementBinders)),
null, null)),
type: this.type, type: this.type,
elementBinders: apiElementBinders, elementBinders: apiElementBinders,
variableBindings: this.variableBindings, variableBindings: this.variableBindings,
textBindings: textNodeExpressions textBindings: textNodeExpressions,
transitiveNgContentCount: transitiveNgContentCount
}); });
} }
} }

View File

@ -1,5 +1,6 @@
import {DOM} from 'angular2/src/dom/dom_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter';
import {isPresent, isBlank, BaseException, isArray} from 'angular2/src/facade/lang'; import {isPresent, isBlank, BaseException, isArray} from 'angular2/src/facade/lang';
import {ListWrapper} from 'angular2/src/facade/collection';
import {DomProtoView, DomProtoViewRef, resolveInternalDomProtoView} from './proto_view'; import {DomProtoView, DomProtoViewRef, resolveInternalDomProtoView} from './proto_view';
import {DomElementBinder} from './element_binder'; import {DomElementBinder} from './element_binder';
@ -11,71 +12,25 @@ import {
cloneAndQueryProtoView, cloneAndQueryProtoView,
queryBoundElements, queryBoundElements,
queryBoundTextNodeIndices, queryBoundTextNodeIndices,
NG_SHADOW_ROOT_ELEMENT_NAME, NG_SHADOW_ROOT_ELEMENT_NAME
isElementWithTag
} from '../util'; } from '../util';
import {CssSelector} from '../compiler/selector';
const NOT_MATCHABLE_SELECTOR = '_not-matchable_';
export function mergeProtoViewsRecursively(protoViewRefs: List<RenderProtoViewRef | List<any>>): export function mergeProtoViewsRecursively(protoViewRefs: List<RenderProtoViewRef | List<any>>):
RenderProtoViewMergeMapping[] {
var target = [];
_mergeProtoViewsRecursively(protoViewRefs, target);
return target;
}
function _mergeProtoViewsRecursively(protoViewRefs: List<RenderProtoViewRef | List<any>>,
target: RenderProtoViewMergeMapping[]): RenderProtoViewRef {
var targetIndex = target.length;
target.push(null);
var resolvedProtoViewRefs = protoViewRefs.map((entry) => {
if (isArray(entry)) {
return _mergeProtoViewsRecursively(<List<any>>entry, target);
} else {
return entry;
}
});
var mapping = mergeProtoViews(resolvedProtoViewRefs);
target[targetIndex] = mapping;
return mapping.mergedProtoViewRef;
}
export function mergeProtoViews(protoViewRefs: RenderProtoViewRef[]): RenderProtoViewMergeMapping {
var hostProtoView = resolveInternalDomProtoView(protoViewRefs[0]);
var mergeableProtoViews: DomProtoView[] = [];
var hostElementIndices: number[] = [];
mergeableProtoViews.push(hostProtoView);
var protoViewIdx = 1;
for (var i = 0; i < hostProtoView.elementBinders.length; i++) {
var binder = hostProtoView.elementBinders[i];
if (binder.hasNestedProtoView) {
var nestedProtoViewRef = protoViewRefs[protoViewIdx++];
if (isPresent(nestedProtoViewRef)) {
mergeableProtoViews.push(resolveInternalDomProtoView(nestedProtoViewRef));
hostElementIndices.push(i);
}
}
}
return _mergeProtoViews(mergeableProtoViews, hostElementIndices);
}
function _mergeProtoViews(mergeableProtoViews: DomProtoView[], hostElementIndices: number[]):
RenderProtoViewMergeMapping { RenderProtoViewMergeMapping {
var clonedProtoViews: ClonedProtoView[] = // clone
mergeableProtoViews.map(domProtoView => cloneAndQueryProtoView(domProtoView, false)); var clonedProtoViews = [];
var hostProtoView: ClonedProtoView = clonedProtoViews[0]; var hostViewAndBinderIndices: number[][] = [];
cloneProtoViews(protoViewRefs, clonedProtoViews, hostViewAndBinderIndices);
var mainProtoView: ClonedProtoView = clonedProtoViews[0];
// modify the DOM // modify the DOM
mergeDom(clonedProtoViews, hostElementIndices); mergeEmbeddedPvsIntoComponentOrRootPv(clonedProtoViews, hostViewAndBinderIndices);
var fragments = [];
mergeComponents(clonedProtoViews, hostViewAndBinderIndices, fragments);
// create a new root element with the changed fragments and elements // create a new root element with the changed fragments and elements
var rootElement = createRootElementFromFragments(hostProtoView.fragments); var rootElement = createRootElementFromFragments(fragments);
var fragmentsRootNodeCount = hostProtoView.fragments.map(fragment => fragment.length); var fragmentsRootNodeCount = fragments.map(fragment => fragment.length);
var rootNode = DOM.content(rootElement); var rootNode = DOM.content(rootElement);
// read out the new element / text node / ElementBinder order // read out the new element / text node / ElementBinder order
@ -90,16 +45,45 @@ function _mergeProtoViews(mergeableProtoViews: DomProtoView[], hostElementIndice
// create element / text index mappings // create element / text index mappings
var mappedElementIndices = calcMappedElementIndices(clonedProtoViews, mergedBoundElements); var mappedElementIndices = calcMappedElementIndices(clonedProtoViews, mergedBoundElements);
var mappedTextIndices = calcMappedTextIndices(clonedProtoViews, mergedBoundTextIndices); var mappedTextIndices = calcMappedTextIndices(clonedProtoViews, mergedBoundTextIndices);
var hostElementIndicesByViewIndex =
calcHostElementIndicesByViewIndex(clonedProtoViews, hostElementIndices);
// create result // create result
var mergedProtoView = DomProtoView.create( var hostElementIndicesByViewIndex =
hostProtoView.original.type, rootElement, fragmentsRootNodeCount, rootTextNodeIndices, calcHostElementIndicesByViewIndex(clonedProtoViews, hostViewAndBinderIndices);
mergedElementBinders, mappedElementIndices, mappedTextIndices, hostElementIndicesByViewIndex); var nestedViewCounts = calcNestedViewCounts(hostViewAndBinderIndices);
return new RenderProtoViewMergeMapping(new DomProtoViewRef(mergedProtoView), var mergedProtoView =
fragmentsRootNodeCount.length, mappedElementIndices, DomProtoView.create(mainProtoView.original.type, rootElement, fragmentsRootNodeCount,
mappedTextIndices, hostElementIndicesByViewIndex); rootTextNodeIndices, mergedElementBinders);
return new RenderProtoViewMergeMapping(
new DomProtoViewRef(mergedProtoView), fragmentsRootNodeCount.length, mappedElementIndices,
mappedTextIndices, hostElementIndicesByViewIndex, nestedViewCounts);
}
function cloneProtoViews(protoViewRefs: List<RenderProtoViewRef | List<any>>,
targetClonedProtoViews: ClonedProtoView[],
targetHostViewAndBinderIndices: number[][]) {
var hostProtoView = resolveInternalDomProtoView(protoViewRefs[0]);
var hostPvIdx = targetClonedProtoViews.length;
targetClonedProtoViews.push(cloneAndQueryProtoView(hostProtoView, false));
if (targetHostViewAndBinderIndices.length === 0) {
targetHostViewAndBinderIndices.push([null, null]);
}
var protoViewIdx = 1;
for (var i = 0; i < hostProtoView.elementBinders.length; i++) {
var binder = hostProtoView.elementBinders[i];
if (binder.hasNestedProtoView) {
var nestedEntry = protoViewRefs[protoViewIdx++];
if (isPresent(nestedEntry)) {
targetHostViewAndBinderIndices.push([hostPvIdx, i]);
if (isArray(nestedEntry)) {
cloneProtoViews(<any[]>nestedEntry, targetClonedProtoViews,
targetHostViewAndBinderIndices);
} else {
targetClonedProtoViews.push(
cloneAndQueryProtoView(resolveInternalDomProtoView(nestedEntry), false));
}
}
}
}
} }
function indexBoundTextNodes(mergableProtoViews: ClonedProtoView[]): Map<Node, any> { function indexBoundTextNodes(mergableProtoViews: ClonedProtoView[]): Map<Node, any> {
@ -112,42 +96,56 @@ function indexBoundTextNodes(mergableProtoViews: ClonedProtoView[]): Map<Node, a
return boundTextNodeMap; return boundTextNodeMap;
} }
function mergeDom(clonedProtoViews: ClonedProtoView[], hostElementIndices: number[]) { function mergeEmbeddedPvsIntoComponentOrRootPv(clonedProtoViews: ClonedProtoView[],
var nestedProtoViewByHostElement: Map<Element, ClonedProtoView> = hostViewAndBinderIndices: number[][]) {
indexProtoViewsByHostElement(clonedProtoViews, hostElementIndices); var nearestHostComponentOrRootPvIndices =
calcNearestHostComponentOrRootPvIndices(clonedProtoViews, hostViewAndBinderIndices);
for (var viewIdx = 1; viewIdx < clonedProtoViews.length; viewIdx++) {
var clonedProtoView = clonedProtoViews[viewIdx];
if (clonedProtoView.original.type === ViewType.EMBEDDED) {
var hostComponentIdx = nearestHostComponentOrRootPvIndices[viewIdx];
var hostPv = clonedProtoViews[hostComponentIdx];
clonedProtoView.fragments.forEach((fragment) => hostPv.fragments.push(fragment));
}
}
}
var hostProtoView = clonedProtoViews[0]; function calcNearestHostComponentOrRootPvIndices(clonedProtoViews: ClonedProtoView[],
var mergableProtoViewIdx = 1; hostViewAndBinderIndices: number[][]): number[] {
hostElementIndices.forEach((boundElementIndex) => { var nearestHostComponentOrRootPvIndices = ListWrapper.createFixedSize(clonedProtoViews.length);
var binder = hostProtoView.original.elementBinders[boundElementIndex]; nearestHostComponentOrRootPvIndices[0] = null;
if (binder.hasNestedProtoView) { for (var viewIdx = 1; viewIdx < hostViewAndBinderIndices.length; viewIdx++) {
var mergableNestedProtoView: ClonedProtoView = clonedProtoViews[mergableProtoViewIdx++]; var hostViewIdx = hostViewAndBinderIndices[viewIdx][0];
if (mergableNestedProtoView.original.type === ViewType.COMPONENT) { var hostProtoView = clonedProtoViews[hostViewIdx];
mergeComponentDom(hostProtoView, boundElementIndex, mergableNestedProtoView, if (hostViewIdx === 0 || hostProtoView.original.type === ViewType.COMPONENT) {
nestedProtoViewByHostElement); nearestHostComponentOrRootPvIndices[viewIdx] = hostViewIdx;
} else { } else {
mergeEmbeddedDom(hostProtoView, mergableNestedProtoView); nearestHostComponentOrRootPvIndices[viewIdx] =
nearestHostComponentOrRootPvIndices[hostViewIdx];
} }
} }
}); return nearestHostComponentOrRootPvIndices;
} }
function indexProtoViewsByHostElement(mergableProtoViews: ClonedProtoView[], function mergeComponents(clonedProtoViews: ClonedProtoView[], hostViewAndBinderIndices: number[][],
hostElementIndices: number[]): Map<Element, ClonedProtoView> { targetFragments: Node[][]) {
var hostProtoView = mergableProtoViews[0]; var hostProtoView = clonedProtoViews[0];
var mergableProtoViewIdx = 1; hostProtoView.fragments.forEach((fragment) => targetFragments.push(fragment));
var nestedProtoViewByHostElement: Map<Element, ClonedProtoView> = new Map();
hostElementIndices.forEach((hostElementIndex) => { for (var viewIdx = 1; viewIdx < clonedProtoViews.length; viewIdx++) {
nestedProtoViewByHostElement.set(hostProtoView.boundElements[hostElementIndex], var hostViewIdx = hostViewAndBinderIndices[viewIdx][0];
mergableProtoViews[mergableProtoViewIdx++]); var hostBinderIdx = hostViewAndBinderIndices[viewIdx][1];
}); var hostProtoView = clonedProtoViews[hostViewIdx];
return nestedProtoViewByHostElement; var clonedProtoView = clonedProtoViews[viewIdx];
if (clonedProtoView.original.type === ViewType.COMPONENT) {
mergeComponent(hostProtoView, hostBinderIdx, clonedProtoView, targetFragments);
}
}
} }
function mergeComponentDom(hostProtoView: ClonedProtoView, boundElementIndex: number, function mergeComponent(hostProtoView: ClonedProtoView, binderIdx: number,
nestedProtoView: ClonedProtoView, nestedProtoView: ClonedProtoView, targetFragments: Node[][]) {
nestedProtoViewByHostElement: Map<Element, ClonedProtoView>) { var hostElement = hostProtoView.boundElements[binderIdx];
var hostElement = hostProtoView.boundElements[boundElementIndex];
// We wrap the fragments into elements so that we can expand <ng-content> // We wrap the fragments into elements so that we can expand <ng-content>
// even for root nodes in the fragment without special casing them. // even for root nodes in the fragment without special casing them.
@ -163,15 +161,15 @@ function mergeComponentDom(hostProtoView: ClonedProtoView, boundElementIndex: nu
// unwrap the fragment elements into arrays of nodes after projecting // unwrap the fragment elements into arrays of nodes after projecting
var fragments = extractFragmentNodesFromElements(fragmentElements); var fragments = extractFragmentNodesFromElements(fragmentElements);
appendComponentNodesToHost(hostProtoView, boundElementIndex, fragments[0]); appendComponentNodesToHost(hostProtoView, binderIdx, fragments[0]);
for (var i = 1; i < fragments.length; i++) { for (var i = 1; i < fragments.length; i++) {
hostProtoView.fragments.push(fragments[i]); targetFragments.push(fragments[i]);
} }
} }
function mapFragmentsIntoElements(fragments: Node[][]): Element[] { function mapFragmentsIntoElements(fragments: Node[][]): Element[] {
return fragments.map((fragment) => { return fragments.map(fragment => {
var fragmentElement = DOM.createTemplate(''); var fragmentElement = DOM.createTemplate('');
fragment.forEach(node => DOM.appendChild(DOM.content(fragmentElement), node)); fragment.forEach(node => DOM.appendChild(DOM.content(fragmentElement), node));
return fragmentElement; return fragmentElement;
@ -195,10 +193,10 @@ function findContentElements(fragmentElements: Element[]): Element[] {
return sortContentElements(contentElements); return sortContentElements(contentElements);
} }
function appendComponentNodesToHost(hostProtoView: ClonedProtoView, boundElementIndex: number, function appendComponentNodesToHost(hostProtoView: ClonedProtoView, binderIdx: number,
componentRootNodes: Node[]) { componentRootNodes: Node[]) {
var hostElement = hostProtoView.boundElements[boundElementIndex]; var hostElement = hostProtoView.boundElements[binderIdx];
var binder = hostProtoView.original.elementBinders[boundElementIndex]; var binder = hostProtoView.original.elementBinders[binderIdx];
if (binder.hasNativeShadowRoot) { if (binder.hasNativeShadowRoot) {
var shadowRootWrapper = DOM.createElement(NG_SHADOW_ROOT_ELEMENT_NAME); var shadowRootWrapper = DOM.createElement(NG_SHADOW_ROOT_ELEMENT_NAME);
for (var i = 0; i < componentRootNodes.length; i++) { for (var i = 0; i < componentRootNodes.length; i++) {
@ -218,40 +216,23 @@ function appendComponentNodesToHost(hostProtoView: ClonedProtoView, boundElement
} }
} }
function mergeEmbeddedDom(parentProtoView: ClonedProtoView, nestedProtoView: ClonedProtoView) {
nestedProtoView.fragments.forEach((fragment) => parentProtoView.fragments.push(fragment));
}
function projectMatchingNodes(selector: string, contentElement: Element, nodes: Node[]): Node[] { function projectMatchingNodes(selector: string, contentElement: Element, nodes: Node[]): Node[] {
var remaining = []; var remaining = [];
var removeContentElement = true;
for (var i = 0; i < nodes.length; i++) { for (var i = 0; i < nodes.length; i++) {
var node = nodes[i]; var node = nodes[i];
var matches = false;
if (isWildcard(selector)) { if (isWildcard(selector)) {
DOM.insertBefore(contentElement, node); matches = true;
} else if (DOM.isElementNode(node)) { } else if (DOM.isElementNode(node) && DOM.elementMatches(node, selector)) {
if (isElementWithTag(node, NG_CONTENT_ELEMENT_NAME)) { matches = true;
// keep the projected content as other <ng-content> elements }
// might want to use it as well. if (matches) {
remaining.push(node);
DOM.setAttribute(contentElement, 'select',
mergeSelectors(selector, DOM.getAttribute(node, 'select')));
removeContentElement = false;
} else {
if (DOM.elementMatches(node, selector)) {
DOM.insertBefore(contentElement, node); DOM.insertBefore(contentElement, node);
} else { } else {
remaining.push(node); remaining.push(node);
} }
} }
} else {
// non projected text nodes
remaining.push(node);
}
}
if (removeContentElement) {
DOM.remove(contentElement); DOM.remove(contentElement);
}
return remaining; return remaining;
} }
@ -259,36 +240,6 @@ function isWildcard(selector): boolean {
return isBlank(selector) || selector.length === 0 || selector == '*'; return isBlank(selector) || selector.length === 0 || selector == '*';
} }
export function mergeSelectors(selector1: string, selector2: string): string {
if (isWildcard(selector1)) {
return isBlank(selector2) ? '' : selector2;
} else if (isWildcard(selector2)) {
return isBlank(selector1) ? '' : selector1;
} else {
var sels1 = CssSelector.parse(selector1);
var sels2 = CssSelector.parse(selector2);
if (sels1.length > 1 || sels2.length > 1) {
throw new BaseException('multiple selectors are not supported in ng-content');
}
var sel1 = sels1[0];
var sel2 = sels2[0];
if (sel1.notSelectors.length > 0 || sel2.notSelectors.length > 0) {
throw new BaseException(':not selector is not supported in ng-content');
}
var merged = new CssSelector();
if (isBlank(sel1.element)) {
merged.setElement(sel2.element);
} else if (isBlank(sel2.element)) {
merged.setElement(sel1.element);
} else {
return NOT_MATCHABLE_SELECTOR;
}
merged.attrs = sel1.attrs.concat(sel2.attrs);
merged.classNames = sel1.classNames.concat(sel2.classNames);
return merged.toString();
}
}
// we need to sort content elements as they can originate from // we need to sort content elements as they can originate from
// different sub views // different sub views
function sortContentElements(contentElements: Element[]): Element[] { function sortContentElements(contentElements: Element[]): Element[] {
@ -394,12 +345,8 @@ function calcMappedElementIndices(clonedProtoViews: ClonedProtoView[],
var mergedBoundElementIndices: Map<Element, number> = indexArray(mergedBoundElements); var mergedBoundElementIndices: Map<Element, number> = indexArray(mergedBoundElements);
var mappedElementIndices = []; var mappedElementIndices = [];
clonedProtoViews.forEach((clonedProtoView) => { clonedProtoViews.forEach((clonedProtoView) => {
clonedProtoView.original.mappedElementIndices.forEach((boundElementIndex) => { clonedProtoView.boundElements.forEach((boundElement) => {
var mappedElementIndex = null; var mappedElementIndex = mergedBoundElementIndices.get(boundElement);
if (isPresent(boundElementIndex)) {
var boundElement = clonedProtoView.boundElements[boundElementIndex];
mappedElementIndex = mergedBoundElementIndices.get(boundElement);
}
mappedElementIndices.push(mappedElementIndex); mappedElementIndices.push(mappedElementIndex);
}); });
}); });
@ -410,12 +357,8 @@ function calcMappedTextIndices(clonedProtoViews: ClonedProtoView[],
mergedBoundTextIndices: Map<Node, number>): number[] { mergedBoundTextIndices: Map<Node, number>): number[] {
var mappedTextIndices = []; var mappedTextIndices = [];
clonedProtoViews.forEach((clonedProtoView) => { clonedProtoViews.forEach((clonedProtoView) => {
clonedProtoView.original.mappedTextIndices.forEach((textNodeIndex) => { clonedProtoView.boundTextNodes.forEach((textNode) => {
var mappedTextIndex = null; var mappedTextIndex = mergedBoundTextIndices.get(textNode);
if (isPresent(textNodeIndex)) {
var textNode = clonedProtoView.boundTextNodes[textNodeIndex];
mappedTextIndex = mergedBoundTextIndices.get(textNode);
}
mappedTextIndices.push(mappedTextIndex); mappedTextIndices.push(mappedTextIndex);
}); });
}); });
@ -423,23 +366,30 @@ function calcMappedTextIndices(clonedProtoViews: ClonedProtoView[],
} }
function calcHostElementIndicesByViewIndex(clonedProtoViews: ClonedProtoView[], function calcHostElementIndicesByViewIndex(clonedProtoViews: ClonedProtoView[],
hostElementIndices: number[]): number[] { hostViewAndBinderIndices: number[][]): number[] {
var mergedElementCount = 0; var hostElementIndices = [null];
var hostElementIndicesByViewIndex = []; var viewElementOffsets = [0];
for (var i = 0; i < clonedProtoViews.length; i++) { var elementIndex = clonedProtoViews[0].original.elementBinders.length;
var clonedProtoView = clonedProtoViews[i]; for (var viewIdx = 1; viewIdx < hostViewAndBinderIndices.length; viewIdx++) {
clonedProtoView.original.hostElementIndicesByViewIndex.forEach((hostElementIndex) => { viewElementOffsets.push(elementIndex);
var mappedHostElementIndex; elementIndex += clonedProtoViews[viewIdx].original.elementBinders.length;
if (isBlank(hostElementIndex)) { var hostViewIdx = hostViewAndBinderIndices[viewIdx][0];
mappedHostElementIndex = i > 0 ? hostElementIndices[i - 1] : null; var hostBinderIdx = hostViewAndBinderIndices[viewIdx][1];
} else { hostElementIndices.push(viewElementOffsets[hostViewIdx] + hostBinderIdx);
mappedHostElementIndex = hostElementIndex + mergedElementCount;
} }
hostElementIndicesByViewIndex.push(mappedHostElementIndex); return hostElementIndices;
}); }
mergedElementCount += clonedProtoView.original.mappedElementIndices.length;
function calcNestedViewCounts(hostViewAndBinderIndices: number[][]): number[] {
var nestedViewCounts = ListWrapper.createFixedSize(hostViewAndBinderIndices.length);
ListWrapper.fill(nestedViewCounts, 0);
for (var viewIdx = hostViewAndBinderIndices.length - 1; viewIdx >= 1; viewIdx--) {
var hostViewAndElementIdx = hostViewAndBinderIndices[viewIdx];
if (isPresent(hostViewAndElementIdx)) {
nestedViewCounts[hostViewAndElementIdx[0]] += nestedViewCounts[viewIdx] + 1;
} }
return hostElementIndicesByViewIndex; }
return nestedViewCounts;
} }
function indexArray(arr: any[]): Map<any, number> { function indexArray(arr: any[]): Map<any, number> {

View File

@ -45,35 +45,6 @@ export function main() {
rootProtoView; rootProtoView;
var renderCompileRequests: any[]; var renderCompileRequests: any[];
function mergeProtoViewsRecursively(
protoViewRefs: List<renderApi.RenderProtoViewRef | List<any>>,
target: renderApi.RenderProtoViewMergeMapping[]): renderApi.RenderProtoViewRef {
var targetIndex = target.length;
target.push(null);
var flattended = protoViewRefs.map(protoViewRefOrArray => {
var resolvedProtoViewRef;
if (isArray(protoViewRefOrArray)) {
resolvedProtoViewRef = mergeProtoViewsRecursively(
<List<renderApi.RenderProtoViewRef>>protoViewRefOrArray, target);
} else {
resolvedProtoViewRef = protoViewRefOrArray;
}
return resolvedProtoViewRef;
});
var merged = [];
flattended.forEach((entry) => {
if (entry instanceof MergedRenderProtoViewRef) {
entry.originals.forEach(ref => merged.push(ref));
} else {
merged.push(entry);
}
});
var result = new MergedRenderProtoViewRef(merged);
target[targetIndex] = new renderApi.RenderProtoViewMergeMapping(result, 1, [], [], []);
return result;
}
function createCompiler(renderCompileResults: function createCompiler(renderCompileResults:
List<renderApi.ProtoViewDto | Promise<renderApi.ProtoViewDto>>, List<renderApi.ProtoViewDto | Promise<renderApi.ProtoViewDto>>,
protoViewFactoryResults: List<AppProtoView>) { protoViewFactoryResults: List<AppProtoView>) {
@ -102,10 +73,10 @@ export function main() {
}); });
renderCompiler.spy('mergeProtoViewsRecursively') renderCompiler.spy('mergeProtoViewsRecursively')
.andCallFake((protoViewRefs: List<renderApi.RenderProtoViewRef | List<any>>) => { .andCallFake((protoViewRefs: List<renderApi.RenderProtoViewRef | List<any>>) => {
var result: renderApi.RenderProtoViewMergeMapping[] = []; return PromiseWrapper.resolve(new renderApi.RenderProtoViewMergeMapping(
mergeProtoViewsRecursively(protoViewRefs, result); new MergedRenderProtoViewRef(protoViewRefs), 1, [], [], [], [null]));
return PromiseWrapper.resolve(result);
}); });
// TODO spy on .compile and return RenderProtoViewRef, same for compileHost
rootProtoView = createRootProtoView(directiveResolver, MainComponent); rootProtoView = createRootProtoView(directiveResolver, MainComponent);
}); });
@ -360,13 +331,12 @@ export function main() {
createCompiler(renderPvDtos, [rootProtoView, mainProtoView, nestedProtoView]); createCompiler(renderPvDtos, [rootProtoView, mainProtoView, nestedProtoView]);
compiler.compileInHost(MainComponent) compiler.compileInHost(MainComponent)
.then((protoViewRef) => { .then((protoViewRef) => {
expect(originalRenderProtoViewRefs(internalProtoView(protoViewRef)))
.toEqual(
[rootProtoView.render, [mainProtoView.render, [nestedProtoView.render]]]);
expect(internalProtoView(protoViewRef).elementBinders[0].nestedProtoView) expect(internalProtoView(protoViewRef).elementBinders[0].nestedProtoView)
.toBe(mainProtoView); .toBe(mainProtoView);
expect(originalRenderProtoViewRefs(mainProtoView))
.toEqual([renderPvDtos[0].render, renderPvDtos[1].render]);
expect(mainProtoView.elementBinders[0].nestedProtoView).toBe(nestedProtoView); expect(mainProtoView.elementBinders[0].nestedProtoView).toBe(nestedProtoView);
expect(originalRenderProtoViewRefs(nestedProtoView))
.toEqual([renderPvDtos[1].render]);
async.done(); async.done();
}); });
})); }));
@ -375,7 +345,8 @@ export function main() {
tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'})); tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'}));
tplResolver.setView(NestedComponent, new viewAnn.View({template: '<div></div>'})); tplResolver.setView(NestedComponent, new viewAnn.View({template: '<div></div>'}));
var viewportProtoView = var viewportProtoView =
createProtoView([createComponentElementBinder(directiveResolver, NestedComponent)]); createProtoView([createComponentElementBinder(directiveResolver, NestedComponent)],
renderApi.ViewType.EMBEDDED);
var mainProtoView = createProtoView([createViewportElementBinder(viewportProtoView)]); var mainProtoView = createProtoView([createViewportElementBinder(viewportProtoView)]);
var nestedProtoView = createProtoView(); var nestedProtoView = createProtoView();
var renderPvDtos = [ var renderPvDtos = [
@ -391,23 +362,11 @@ export function main() {
.then((protoViewRef) => { .then((protoViewRef) => {
expect(internalProtoView(protoViewRef).elementBinders[0].nestedProtoView) expect(internalProtoView(protoViewRef).elementBinders[0].nestedProtoView)
.toBe(mainProtoView); .toBe(mainProtoView);
expect(originalRenderProtoViewRefs(mainProtoView)) expect(originalRenderProtoViewRefs(internalProtoView(protoViewRef)))
.toEqual([ .toEqual([rootProtoView.render, [mainProtoView.render, null]]);
renderPvDtos[0]
.render,
renderPvDtos[0].elementBinders[0].nestedProtoView.render,
renderPvDtos[1].render
]);
expect(viewportProtoView.elementBinders[0].nestedProtoView).toBe(nestedProtoView); expect(viewportProtoView.elementBinders[0].nestedProtoView).toBe(nestedProtoView);
expect(originalRenderProtoViewRefs(viewportProtoView)) expect(originalRenderProtoViewRefs(viewportProtoView))
.toEqual([ .toEqual([viewportProtoView.render, [nestedProtoView.render]]);
renderPvDtos[0]
.elementBinders[0]
.nestedProtoView.render,
renderPvDtos[1].render
]);
expect(originalRenderProtoViewRefs(nestedProtoView))
.toEqual([renderPvDtos[1].render]);
async.done(); async.done();
}); });
})); }));
@ -520,7 +479,8 @@ export function main() {
inject([AsyncTestCompleter], (async) => { inject([AsyncTestCompleter], (async) => {
tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'})); tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'}));
var viewportProtoView = var viewportProtoView =
createProtoView([createComponentElementBinder(directiveResolver, MainComponent)]); createProtoView([createComponentElementBinder(directiveResolver, MainComponent)],
renderApi.ViewType.EMBEDDED);
var mainProtoView = createProtoView([createViewportElementBinder(viewportProtoView)]); var mainProtoView = createProtoView([createViewportElementBinder(viewportProtoView)]);
var renderPvDtos = [ var renderPvDtos = [
createRenderProtoView([ createRenderProtoView([
@ -539,20 +499,39 @@ export function main() {
.nestedProtoView) .nestedProtoView)
.toBe(mainProtoView); .toBe(mainProtoView);
// In case of a cycle, don't merge the embedded proto views into the component! // In case of a cycle, don't merge the embedded proto views into the component!
expect(originalRenderProtoViewRefs(mainProtoView)) expect(originalRenderProtoViewRefs(internalProtoView(protoViewRef)))
.toEqual([renderPvDtos[0].render, null]); .toEqual([rootProtoView.render, [mainProtoView.render, null]]);
expect(originalRenderProtoViewRefs(viewportProtoView)) expect(originalRenderProtoViewRefs(viewportProtoView))
.toEqual([ .toEqual([viewportProtoView.render, [mainProtoView.render, null]]);
renderPvDtos[0]
.elementBinders[0]
.nestedProtoView.render,
renderPvDtos[1].render,
null
]);
async.done(); async.done();
}); });
})); }));
it('should throw on recursive components that are connected via an embedded ProtoView with <ng-content>',
inject([AsyncTestCompleter], (async) => {
tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'}));
var viewportProtoView =
createProtoView([createComponentElementBinder(directiveResolver, MainComponent)],
renderApi.ViewType.EMBEDDED, true);
var mainProtoView = createProtoView([createViewportElementBinder(viewportProtoView)]);
var renderPvDtos = [
createRenderProtoView([
createRenderViewportElementBinder(createRenderProtoView(
[createRenderComponentElementBinder(0)], renderApi.ViewType.EMBEDDED))
]),
createRenderProtoView()
];
var compiler = createCompiler(renderPvDtos, [rootProtoView, mainProtoView]);
PromiseWrapper.catchError(compiler.compileInHost(MainComponent), (e) => {
expect(() => { throw e; })
.toThrowError(
`<ng-content> is used within the recursive path of ${stringify(MainComponent)}`);
async.done();
return null;
});
}));
it('should create host proto views', inject([AsyncTestCompleter], (async) => { it('should create host proto views', inject([AsyncTestCompleter], (async) => {
tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'})); tplResolver.setView(MainComponent, new viewAnn.View({template: '<div></div>'}));
var rootProtoView = var rootProtoView =
@ -581,8 +560,13 @@ function createDirectiveBinding(directiveResolver, type): DirectiveBinding {
return DirectiveBinding.createFromType(type, annotation); return DirectiveBinding.createFromType(type, annotation);
} }
function createProtoView(elementBinders = null): AppProtoView { function createProtoView(elementBinders = null, type: renderApi.ViewType = null,
var pv = new AppProtoView(null, null, null, new Map(), null); isEmbeddedFragment: boolean = false): AppProtoView {
if (isBlank(type)) {
type = renderApi.ViewType.COMPONENT;
}
var pv = new AppProtoView(type, isEmbeddedFragment, new renderApi.RenderProtoViewRef(), null,
null, new Map(), null);
if (isBlank(elementBinders)) { if (isBlank(elementBinders)) {
elementBinders = []; elementBinders = [];
} }
@ -623,7 +607,8 @@ function createRenderViewportElementBinder(nestedProtoView): renderApi.ElementBi
} }
function createRootProtoView(directiveResolver, type): AppProtoView { function createRootProtoView(directiveResolver, type): AppProtoView {
return createProtoView([createComponentElementBinder(directiveResolver, type)]); return createProtoView([createComponentElementBinder(directiveResolver, type)],
renderApi.ViewType.HOST);
} }
@Component({selector: 'main-comp'}) @Component({selector: 'main-comp'})

View File

@ -182,7 +182,8 @@ function createRenderProtoView(elementBinders = null, type: renderApi.ViewType =
elementBinders: elementBinders, elementBinders: elementBinders,
type: type, type: type,
variableBindings: variableBindings, variableBindings: variableBindings,
textBindings: [] textBindings: [],
transitiveNgContentCount: 0
}); });
} }

View File

@ -289,13 +289,32 @@ function calcHostElementIndicesByViewIndex(pv: AppProtoView, elementOffset = 0,
return target; return target;
} }
function countNestedProtoViews(pv: AppProtoView, target: number[] = null): number[] {
if (isBlank(target)) {
target = [];
}
target.push(null);
var resultIndex = target.length - 1;
var count = 0;
for (var binderIdx = 0; binderIdx < pv.elementBinders.length; binderIdx++) {
var binder = pv.elementBinders[binderIdx];
if (isPresent(binder.nestedProtoView)) {
var nextResultIndex = target.length;
countNestedProtoViews(binder.nestedProtoView, target);
count += target[nextResultIndex] + 1;
}
}
target[resultIndex] = count;
return target;
}
function _createProtoView(type: ViewType, binders: ElementBinder[] = null) { function _createProtoView(type: ViewType, binders: ElementBinder[] = null) {
if (isBlank(binders)) { if (isBlank(binders)) {
binders = []; binders = [];
} }
var protoChangeDetector = <any>new SpyProtoChangeDetector(); var protoChangeDetector = <any>new SpyProtoChangeDetector();
protoChangeDetector.spy('instantiate').andReturn(new SpyChangeDetector()); protoChangeDetector.spy('instantiate').andReturn(new SpyChangeDetector());
var res = new AppProtoView(type, protoChangeDetector, null, null, 0); var res = new AppProtoView(type, null, null, protoChangeDetector, null, null, 0);
res.elementBinders = binders; res.elementBinders = binders;
var mappedElementIndices = ListWrapper.createFixedSize(countNestedElementBinders(res)); var mappedElementIndices = ListWrapper.createFixedSize(countNestedElementBinders(res));
for (var i = 0; i < binders.length; i++) { for (var i = 0; i < binders.length; i++) {
@ -304,9 +323,11 @@ function _createProtoView(type: ViewType, binders: ElementBinder[] = null) {
binder.protoElementInjector.index = i; binder.protoElementInjector.index = i;
} }
var hostElementIndicesByViewIndex = calcHostElementIndicesByViewIndex(res); var hostElementIndicesByViewIndex = calcHostElementIndicesByViewIndex(res);
res.mergeMapping = new AppProtoViewMergeMapping( if (type === ViewType.EMBEDDED || type === ViewType.HOST) {
new RenderProtoViewMergeMapping(null, hostElementIndicesByViewIndex.length, res.mergeMapping = new AppProtoViewMergeMapping(new RenderProtoViewMergeMapping(
mappedElementIndices, [], hostElementIndicesByViewIndex)); null, hostElementIndicesByViewIndex.length, mappedElementIndices, [],
hostElementIndicesByViewIndex, countNestedProtoViews(res)));
}
return res; return res;
} }

View File

@ -24,7 +24,9 @@ export function main() {
function createViewPool({capacity}): AppViewPool { return new AppViewPool(capacity); } function createViewPool({capacity}): AppViewPool { return new AppViewPool(capacity); }
function createProtoView() { return new AppProtoView(null, null, null, null, null); } function createProtoView() {
return new AppProtoView(null, null, null, null, null, null, null);
}
function createView(pv) { function createView(pv) {
return new AppView(null, pv, null, null, null, null, new Map(), null, null); return new AppView(null, pv, null, null, null, null, new Map(), null, null);

View File

@ -53,7 +53,7 @@ export function main() {
{componentId: 'someComponent', template: '{{a}}', directives: []}) {componentId: 'someComponent', template: '{{a}}', directives: []})
]) ])
.then((protoViewMergeMappings) => { .then((protoViewMergeMappings) => {
var rootView = tb.createView(protoViewMergeMappings[0]); var rootView = tb.createView(protoViewMergeMappings);
tb.renderer.setText(rootView.viewRef, 0, 'hello'); tb.renderer.setText(rootView.viewRef, 0, 'hello');
expect(rootView.hostElement).toHaveText('hello'); expect(rootView.hostElement).toHaveText('hello');
@ -92,7 +92,7 @@ export function main() {
expect(DOM.getAttribute(el, 'some-attr')).toEqual('someValue'); expect(DOM.getAttribute(el, 'some-attr')).toEqual('someValue');
}; };
var rootView = tb.createView(protoViewMergeMappings[0]); var rootView = tb.createView(protoViewMergeMappings);
// root element // root element
checkSetters(elRef(rootView.viewRef, 0), rootView.hostElement); checkSetters(elRef(rootView.viewRef, 0), rootView.hostElement);
// nested elements // nested elements
@ -114,7 +114,7 @@ export function main() {
}) })
]) ])
.then((protoViewMergeMappings) => { .then((protoViewMergeMappings) => {
var rootView = tb.createView(protoViewMergeMappings[0]); var rootView = tb.createView(protoViewMergeMappings);
var el = DOM.childNodes(rootView.hostElement)[0]; var el = DOM.childNodes(rootView.hostElement)[0];
tb.renderer.setElementProperty(elRef(rootView.viewRef, 1), 'maxLength', '20'); tb.renderer.setElementProperty(elRef(rootView.viewRef, 1), 'maxLength', '20');
expect(DOM.getAttribute(<HTMLInputElement>el, 'ng-reflect-max-length')) expect(DOM.getAttribute(<HTMLInputElement>el, 'ng-reflect-max-length'))
@ -138,7 +138,7 @@ export function main() {
}) })
]) ])
.then((protoViewMergeMappings) => { .then((protoViewMergeMappings) => {
var rootView = tb.createView(protoViewMergeMappings[0]); var rootView = tb.createView(protoViewMergeMappings);
var el = DOM.childNodes(rootView.hostElement)[0]; var el = DOM.childNodes(rootView.hostElement)[0];
tb.renderer.setElementProperty(elRef(rootView.viewRef, 1), 'maxLength', '20'); tb.renderer.setElementProperty(elRef(rootView.viewRef, 1), 'maxLength', '20');
expect(DOM.getAttribute(<HTMLInputElement>el, 'ng-reflect-max-length')) expect(DOM.getAttribute(<HTMLInputElement>el, 'ng-reflect-max-length'))
@ -160,7 +160,7 @@ export function main() {
}) })
]) ])
.then((protoViewMergeMappings) => { .then((protoViewMergeMappings) => {
var rootView = tb.createView(protoViewMergeMappings[0]); var rootView = tb.createView(protoViewMergeMappings);
tb.renderer.invokeElementMethod(elRef(rootView.viewRef, 1), 'setAttribute', tb.renderer.invokeElementMethod(elRef(rootView.viewRef, 1), 'setAttribute',
['a', 'b']); ['a', 'b']);
@ -183,7 +183,7 @@ export function main() {
}) })
]) ])
.then((protoViewMergeMappings) => { .then((protoViewMergeMappings) => {
var rootView = tb.createView(protoViewMergeMappings[0]); var rootView = tb.createView(protoViewMergeMappings);
var elr = elRef(rootView.viewRef, 1); var elr = elRef(rootView.viewRef, 1);
expect(rootView.hostElement).toHaveText(''); expect(rootView.hostElement).toHaveText('');
@ -208,7 +208,7 @@ export function main() {
}) })
]) ])
.then((protoViewMergeMappings) => { .then((protoViewMergeMappings) => {
var rootView = tb.createView(protoViewMergeMappings[0]); var rootView = tb.createView(protoViewMergeMappings);
var elr = elRef(rootView.viewRef, 1); var elr = elRef(rootView.viewRef, 1);
expect(rootView.hostElement).toHaveText(''); expect(rootView.hostElement).toHaveText('');
@ -233,8 +233,8 @@ export function main() {
directives: [] directives: []
}) })
]) ])
.then((protoViewDtos) => { .then((protoViewMergeMappings) => {
var rootView = tb.createView(protoViewDtos[0]); var rootView = tb.createView(protoViewMergeMappings);
tb.triggerEvent(elRef(rootView.viewRef, 1), 'change'); tb.triggerEvent(elRef(rootView.viewRef, 1), 'change');
var eventEntry = rootView.events[0]; var eventEntry = rootView.events[0];
@ -263,7 +263,7 @@ export function main() {
{componentId: 'someComponent', template: 'hello', directives: []}) {componentId: 'someComponent', template: 'hello', directives: []})
]) ])
.then((protoViewMergeMappings) => { .then((protoViewMergeMappings) => {
var rootView = tb.createView(protoViewMergeMappings[0]); var rootView = tb.createView(protoViewMergeMappings);
expect(DOM.getShadowRoot(rootView.hostElement)).toHaveText('hello'); expect(DOM.getShadowRoot(rootView.hostElement)).toHaveText('hello');
async.done(); async.done();
}); });

View File

@ -82,14 +82,13 @@ export class DomTestbed {
return PromiseWrapper.all(promises); return PromiseWrapper.all(promises);
} }
merge(protoViews: merge(protoViews: List<ProtoViewDto | RenderProtoViewRef>): Promise<RenderProtoViewMergeMapping> {
List<ProtoViewDto | RenderProtoViewRef>): Promise<RenderProtoViewMergeMapping[]> {
return this.compiler.mergeProtoViewsRecursively(collectMergeRenderProtoViewsRecurse( return this.compiler.mergeProtoViewsRecursively(collectMergeRenderProtoViewsRecurse(
<ProtoViewDto>protoViews[0], ListWrapper.slice(protoViews, 1))); <ProtoViewDto>protoViews[0], ListWrapper.slice(protoViews, 1)));
} }
compileAndMerge(host: DirectiveMetadata, compileAndMerge(host: DirectiveMetadata,
componentViews: ViewDefinition[]): Promise<RenderProtoViewMergeMapping[]> { componentViews: ViewDefinition[]): Promise<RenderProtoViewMergeMapping> {
return this.compile(host, componentViews).then(protoViewDtos => this.merge(protoViewDtos)); return this.compile(host, componentViews).then(protoViewDtos => this.merge(protoViewDtos));
} }

View File

@ -133,7 +133,7 @@ export function main() {
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">A(<b class="ng-binding" idx="2">B(<div class="x y"></div>)</b>)</a></root>' '<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">A(<b class="ng-binding" idx="2">B(<div class="x y"></div>)</b>)</a></root>'
])); ]));
it('should keep non projected embedded views (so that they can be moved manually)', it('should keep non projected embedded views as fragments (so that they can be moved manually)',
runAndAssert( runAndAssert(
'root', ['<a><template class="x">b</template></a>', ''], 'root', ['<a><template class="x">b</template></a>', ''],
['<root class="ng-binding" idx="0"><a class="ng-binding" idx="1"></a></root>', 'b'])); ['<root class="ng-binding" idx="0"><a class="ng-binding" idx="1"></a></root>', 'b']));
@ -145,13 +145,6 @@ export function main() {
'b' 'b'
])); ]));
it('should project embedded views and match the single root element',
runAndAssert(
'root', ['<a><div class="x" *ng-if></div></a>', 'A(<ng-content></ng-content>)'], [
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">A(<template class="ng-binding" idx="2" ng-if=""></template>)</a></root>',
'<div *ng-if="" class="x"></div>'
]));
it('should project nodes using the ng-content in embedded views', it('should project nodes using the ng-content in embedded views',
runAndAssert('root', ['<a>b</a>', 'A(<ng-content *ng-if></ng-content>)'], [ runAndAssert('root', ['<a>b</a>', 'A(<ng-content *ng-if></ng-content>)'], [
'<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">A(<template class="ng-binding" idx="2" ng-if=""></template>)</a></root>', '<root class="ng-binding" idx="0"><a class="ng-binding" idx="1">A(<template class="ng-binding" idx="2" ng-if=""></template>)</a></root>',
@ -258,7 +251,7 @@ function runAndAssert(hostElementName: string, componentTemplates: string[],
directives: [aComp, bComp, cComp] directives: [aComp, bComp, cComp]
}))) })))
.then((mergeMappings) => { .then((mergeMappings) => {
expect(stringify(mergeMappings[0])).toEqual(expectedFragments); expect(stringify(mergeMappings)).toEqual(expectedFragments);
async.done(); async.done();
}); });
}); });

View File

@ -1,56 +0,0 @@
import {
AsyncTestCompleter,
beforeEach,
ddescribe,
describe,
el,
expect,
iit,
inject,
it,
xit,
beforeEachBindings,
SpyObject,
stringifyElement
} from 'angular2/test_lib';
import {mergeSelectors} from 'angular2/src/render/dom/view/proto_view_merger';
export function main() {
describe('ProtoViewMerger test', () => {
describe('mergeSelectors', () => {
it('should merge empty selectors', () => {
expect(mergeSelectors('', 'a')).toEqual('a');
expect(mergeSelectors('a', '')).toEqual('a');
expect(mergeSelectors('', '')).toEqual('');
});
it('should merge wildcard selectors', () => {
expect(mergeSelectors('*', 'a')).toEqual('a');
expect(mergeSelectors('a', '*')).toEqual('a');
expect(mergeSelectors('*', '*')).toEqual('*');
});
it('should merge 2 element selectors',
() => { expect(mergeSelectors('a', 'b')).toEqual('_not-matchable_'); });
it('should merge elements and non element selector', () => {
expect(mergeSelectors('a', '.b')).toEqual('a.b');
expect(mergeSelectors('.b', 'a')).toEqual('a.b');
});
it('should merge attributes', () => {
expect(mergeSelectors('[a]', '[b]')).toEqual('[a][b]');
expect(mergeSelectors('[a][b]', '[c][d]')).toEqual('[a][b][c][d]');
expect(mergeSelectors('[a=1]', '[b=2]')).toEqual('[a=1][b=2]');
});
it('should merge classes', () => {
expect(mergeSelectors('.a', '.b')).toEqual('.a.b');
expect(mergeSelectors('.a.b', '.c.d')).toEqual('.a.b.c.d');
});
});
});
}

View File

@ -30,7 +30,7 @@ export function main() {
binders = []; binders = [];
} }
var rootEl = DOM.createTemplate('<div></div>'); var rootEl = DOM.createTemplate('<div></div>');
return DomProtoView.create(null, <Element>rootEl, [1], [], binders, null, null, null); return DomProtoView.create(null, <Element>rootEl, [1], [], binders);
} }
function createElementBinder() { return new DomElementBinder({textNodeIndices: []}); } function createElementBinder() { return new DomElementBinder({textNodeIndices: []}); }