From 1371128cab1e672ec3cb4834b23dec2c7e1e1e58 Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Fri, 31 Jul 2015 10:58:24 -0700 Subject: [PATCH] fix(render): allow to configure when templates are serialized to strings Introduces the injectable `TemplateCloner` that can be configured via the new token `MAX_IN_MEMORY_ELEMENTS_PER_TEMPLATE_TOKEN`. Also replaces `document.adoptNode` with `document.importNode` as otherwise custom elements are not triggered in chrome 43. Closes #3418 Closes #3433 --- modules/angular2/render.ts | 3 +- .../angular2/src/core/application_common.ts | 6 +- modules/angular2/src/dom/parse5_adapter.ts | 2 +- .../src/render/dom/compiler/compiler.ts | 18 ++-- .../angular2/src/render/dom/dom_renderer.ts | 9 +- modules/angular2/src/render/dom/dom_tokens.ts | 6 ++ .../src/render/dom/template_cloner.ts | 47 +++++++++ modules/angular2/src/render/dom/util.ts | 53 ++-------- .../src/render/dom/view/proto_view.ts | 14 +-- .../src/render/dom/view/proto_view_builder.ts | 14 +-- .../src/render/dom/view/proto_view_merger.ts | 42 +++++--- modules/angular2/src/render/render.ts | 1 + .../angular2/src/test_lib/test_injector.ts | 6 +- .../template_compiler/generator.dart | 12 ++- .../src/web-workers/ui/di_bindings.ts | 6 +- .../compiler/projection_integration_spec.ts | 49 ++++++++++ .../dom/compiler/compiler_common_tests.ts | 11 +-- .../test/render/dom/template_cloner_spec.ts | 67 +++++++++++++ modules/angular2/test/render/dom/util_spec.ts | 97 ------------------- .../dom/view/proto_view_builder_spec.ts | 29 +++--- .../proto_view_merger_integration_spec.ts | 20 ++-- .../test/render/dom/view/view_spec.ts | 4 +- .../test/web-workers/worker/renderer_spec.ts | 3 +- .../src/compiler/compiler_benchmark.ts | 13 ++- 24 files changed, 310 insertions(+), 222 deletions(-) create mode 100644 modules/angular2/src/render/dom/template_cloner.ts create mode 100644 modules/angular2/test/render/dom/template_cloner_spec.ts delete mode 100644 modules/angular2/test/render/dom/util_spec.ts diff --git a/modules/angular2/render.ts b/modules/angular2/render.ts index 5cfac5997d30f3..e65fa9d0283bb8 100644 --- a/modules/angular2/render.ts +++ b/modules/angular2/render.ts @@ -17,5 +17,6 @@ export { ViewDefinition, DOCUMENT_TOKEN, APP_ID_TOKEN, - DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES + DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES, + MAX_IN_MEMORY_ELEMENTS_PER_TEMPLATE_TOKEN } from './src/render/render'; diff --git a/modules/angular2/src/core/application_common.ts b/modules/angular2/src/core/application_common.ts index a21f2e7a6067a9..39afa709ded296 100644 --- a/modules/angular2/src/core/application_common.ts +++ b/modules/angular2/src/core/application_common.ts @@ -59,7 +59,9 @@ import { DOCUMENT_TOKEN, DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES, DefaultDomCompiler, - APP_ID_RANDOM_BINDING + APP_ID_RANDOM_BINDING, + MAX_IN_MEMORY_ELEMENTS_PER_TEMPLATE_TOKEN, + TemplateCloner } from 'angular2/src/render/render'; import {ElementSchemaRegistry} from 'angular2/src/render/dom/schema/element_schema_registry'; import {DomElementSchemaRegistry} from 'angular2/src/render/dom/schema/dom_element_schema_registry'; @@ -114,6 +116,8 @@ function _injectorBindings(appComponentType): List> { DomRenderer, bind(Renderer).toAlias(DomRenderer), APP_ID_RANDOM_BINDING, + TemplateCloner, + bind(MAX_IN_MEMORY_ELEMENTS_PER_TEMPLATE_TOKEN).toValue(20), DefaultDomCompiler, bind(ElementSchemaRegistry).toValue(new DomElementSchemaRegistry()), bind(RenderCompiler).toAlias(DefaultDomCompiler), diff --git a/modules/angular2/src/dom/parse5_adapter.ts b/modules/angular2/src/dom/parse5_adapter.ts index daa7a9c54eda3c..a596417fbe333c 100644 --- a/modules/angular2/src/dom/parse5_adapter.ts +++ b/modules/angular2/src/dom/parse5_adapter.ts @@ -77,7 +77,7 @@ export class Parse5DomAdapter extends DomAdapter { return res; } elementMatches(node, selector: string, matcher = null): boolean { - if (!selector || selector === '*') { + if (this.isElementNode(node) && selector === '*') { return true; } var result = false; diff --git a/modules/angular2/src/render/dom/compiler/compiler.ts b/modules/angular2/src/render/dom/compiler/compiler.ts index 2b3d335b439fb9..16f03bef9cd9f7 100644 --- a/modules/angular2/src/render/dom/compiler/compiler.ts +++ b/modules/angular2/src/render/dom/compiler/compiler.ts @@ -24,6 +24,7 @@ import {DOCUMENT_TOKEN, APP_ID_TOKEN} from '../dom_tokens'; import {Inject} from 'angular2/di'; import {SharedStylesHost} from '../view/shared_styles_host'; import {prependAll} from '../util'; +import {TemplateCloner} from '../template_cloner'; /** * The compiler loads and translates the html templates of components into @@ -32,8 +33,8 @@ import {prependAll} from '../util'; */ export class DomCompiler extends RenderCompiler { constructor(private _schemaRegistry: ElementSchemaRegistry, - private _stepFactory: CompileStepFactory, private _viewLoader: ViewLoader, - private _sharedStylesHost: SharedStylesHost) { + private _templateCloner: TemplateCloner, private _stepFactory: CompileStepFactory, + private _viewLoader: ViewLoader, private _sharedStylesHost: SharedStylesHost) { super(); } @@ -65,7 +66,8 @@ export class DomCompiler extends RenderCompiler { mergeProtoViewsRecursively( protoViewRefs: List>): Promise { - return PromiseWrapper.resolve(pvm.mergeProtoViewsRecursively(protoViewRefs)); + return PromiseWrapper.resolve( + pvm.mergeProtoViewsRecursively(this._templateCloner, protoViewRefs)); } _compileView(viewDef: ViewDefinition, templateAndStyles: TemplateAndStyles, @@ -87,7 +89,7 @@ export class DomCompiler extends RenderCompiler { } return PromiseWrapper.resolve( - compileElements[0].inheritedProtoView.build(this._schemaRegistry)); + compileElements[0].inheritedProtoView.build(this._schemaRegistry, this._templateCloner)); } _normalizeViewEncapsulationIfThereAreNoStyles(viewDef: ViewDefinition): ViewDefinition { @@ -108,8 +110,10 @@ export class DomCompiler extends RenderCompiler { @Injectable() export class DefaultDomCompiler extends DomCompiler { - constructor(schemaRegistry: ElementSchemaRegistry, parser: Parser, viewLoader: ViewLoader, - sharedStylesHost: SharedStylesHost, @Inject(APP_ID_TOKEN) appId: any) { - super(schemaRegistry, new DefaultStepFactory(parser, appId), viewLoader, sharedStylesHost); + constructor(schemaRegistry: ElementSchemaRegistry, templateCloner: TemplateCloner, parser: Parser, + viewLoader: ViewLoader, sharedStylesHost: SharedStylesHost, + @Inject(APP_ID_TOKEN) appId: any) { + super(schemaRegistry, templateCloner, new DefaultStepFactory(parser, appId), viewLoader, + sharedStylesHost); } } diff --git a/modules/angular2/src/render/dom/dom_renderer.ts b/modules/angular2/src/render/dom/dom_renderer.ts index 7b8fb6a1053180..32350ca698c4bf 100644 --- a/modules/angular2/src/render/dom/dom_renderer.ts +++ b/modules/angular2/src/render/dom/dom_renderer.ts @@ -32,6 +32,8 @@ import { RenderViewWithFragments } from '../api'; +import {TemplateCloner} from './template_cloner'; + import {DOCUMENT_TOKEN, DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES} from './dom_tokens'; const REFLECT_PREFIX: string = 'ng-reflect-'; @@ -41,8 +43,9 @@ export class DomRenderer extends Renderer { _document; _reflectPropertiesAsAttributes: boolean; - constructor(public _eventManager: EventManager, private _domSharedStylesHost: DomSharedStylesHost, - @Inject(DOCUMENT_TOKEN) document, + constructor(private _eventManager: EventManager, + private _domSharedStylesHost: DomSharedStylesHost, + private _templateCloner: TemplateCloner, @Inject(DOCUMENT_TOKEN) document, @Inject(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES) reflectPropertiesAsAttributes: boolean) { super(); @@ -206,7 +209,7 @@ export class DomRenderer extends Renderer { } _createView(protoView: DomProtoView, inplaceElement: HTMLElement): RenderViewWithFragments { - var clonedProtoView = cloneAndQueryProtoView(protoView, true); + var clonedProtoView = cloneAndQueryProtoView(this._templateCloner, protoView, true); var boundElements = clonedProtoView.boundElements; diff --git a/modules/angular2/src/render/dom/dom_tokens.ts b/modules/angular2/src/render/dom/dom_tokens.ts index 1369fcbe15c55f..2e1043d0d375f9 100644 --- a/modules/angular2/src/render/dom/dom_tokens.ts +++ b/modules/angular2/src/render/dom/dom_tokens.ts @@ -18,6 +18,12 @@ export const APP_ID_TOKEN: OpaqueToken = CONST_EXPR(new OpaqueToken('AppId')); export var APP_ID_RANDOM_BINDING: Binding = bind(APP_ID_TOKEN).toFactory(() => `${randomChar()}${randomChar()}${randomChar()}`, []); +/** + * Defines when a compiled template should be stored as a string + * rather than keeping its Nodes to preserve memory. + */ +export const MAX_IN_MEMORY_ELEMENTS_PER_TEMPLATE_TOKEN: OpaqueToken = + CONST_EXPR(new OpaqueToken('MaxInMemoryElementsPerTemplate')); function randomChar(): string { return StringWrapper.fromCharCode(97 + Math.floor(Math.random() * 25)); diff --git a/modules/angular2/src/render/dom/template_cloner.ts b/modules/angular2/src/render/dom/template_cloner.ts new file mode 100644 index 00000000000000..da5bbe05d52287 --- /dev/null +++ b/modules/angular2/src/render/dom/template_cloner.ts @@ -0,0 +1,47 @@ +import {isString} from 'angular2/src/facade/lang'; +import {Injectable, Inject} from 'angular2/di'; +import {DOM} from 'angular2/src/dom/dom_adapter'; +import {MAX_IN_MEMORY_ELEMENTS_PER_TEMPLATE_TOKEN} from './dom_tokens'; + +@Injectable() +export class TemplateCloner { + maxInMemoryElementsPerTemplate: number; + + constructor(@Inject(MAX_IN_MEMORY_ELEMENTS_PER_TEMPLATE_TOKEN) maxInMemoryElementsPerTemplate) { + this.maxInMemoryElementsPerTemplate = maxInMemoryElementsPerTemplate; + } + + prepareForClone(templateRoot: Element): Element | string { + var elementCount = DOM.querySelectorAll(DOM.content(templateRoot), '*').length; + if (this.maxInMemoryElementsPerTemplate >= 0 && + elementCount >= this.maxInMemoryElementsPerTemplate) { + return DOM.getInnerHTML(templateRoot); + } else { + return templateRoot; + } + } + + cloneContent(preparedTemplateRoot: Element | string, importNode: boolean): Node { + var templateContent; + if (isString(preparedTemplateRoot)) { + templateContent = DOM.content(DOM.createTemplate(preparedTemplateRoot)); + if (importNode) { + // Attention: We can't use document.adoptNode here + // as this does NOT wake up custom elements in Chrome 43 + // TODO: Use div.innerHTML instead of template.innerHTML when we + // have code to support the various special cases and + // don't use importNode additionally (e.g. for , svg elements, ...) + // see https://github.com/angular/angular/issues/3364 + templateContent = DOM.importIntoDoc(templateContent); + } + } else { + templateContent = DOM.content(preparedTemplateRoot); + if (importNode) { + templateContent = DOM.importIntoDoc(templateContent); + } else { + templateContent = DOM.clone(templateContent); + } + } + return templateContent; + } +} \ No newline at end of file diff --git a/modules/angular2/src/render/dom/util.ts b/modules/angular2/src/render/dom/util.ts index ce646ad2e83e49..643dd63847b675 100644 --- a/modules/angular2/src/render/dom/util.ts +++ b/modules/angular2/src/render/dom/util.ts @@ -3,6 +3,7 @@ import {DOM} from 'angular2/src/dom/dom_adapter'; import {ListWrapper} from 'angular2/src/facade/collection'; import {DomProtoView} from './view/proto_view'; import {DomElementBinder} from './view/element_binder'; +import {TemplateCloner} from './template_cloner'; export const NG_BINDING_CLASS_SELECTOR = '.ng-binding'; export const NG_BINDING_CLASS = 'ng-binding'; @@ -57,9 +58,9 @@ export class ClonedProtoView { public boundElements: Element[], public boundTextNodes: Node[]) {} } -export function cloneAndQueryProtoView(pv: DomProtoView, importIntoDocument: boolean): - ClonedProtoView { - var templateContent = pv.cloneableTemplate.clone(importIntoDocument); +export function cloneAndQueryProtoView(templateCloner: TemplateCloner, pv: DomProtoView, + importIntoDocument: boolean): ClonedProtoView { + var templateContent = templateCloner.cloneContent(pv.cloneableTemplate, importIntoDocument); var boundElements = queryBoundElements(templateContent, pv.isSingleElementFragment); var boundTextNodes = queryBoundTextNodes(templateContent, pv.rootTextNodeIndices, boundElements, @@ -77,6 +78,10 @@ function queryFragments(templateContent: Node, fragmentsRootNodeCount: number[]) for (var fragmentIndex = 0; fragmentIndex < fragments.length; fragmentIndex++) { var fragment = ListWrapper.createFixedSize(fragmentsRootNodeCount[fragmentIndex]); fragments[fragmentIndex] = fragment; + // Note: the 2nd, 3rd, ... fragments are separated by each other via a '|' + if (fragmentIndex >= 1) { + childNode = DOM.nextSibling(childNode); + } for (var i = 0; i < fragment.length; i++) { fragment[i] = childNode; childNode = DOM.nextSibling(childNode); @@ -141,45 +146,3 @@ export function prependAll(parentNode: Node, nodes: Node[]) { lastInsertedNode = node; }); } - -export interface CloneableTemplate { clone(importIntoDoc: boolean): Node; } - -export class SerializedCloneableTemplate implements CloneableTemplate { - templateString: string; - constructor(templateRoot: Element) { this.templateString = DOM.getInnerHTML(templateRoot); } - clone(importIntoDoc: boolean): Node { - var result = DOM.content(DOM.createTemplate(this.templateString)); - if (importIntoDoc) { - result = DOM.adoptNode(result); - } - return result; - } -} - -export class ReferenceCloneableTemplate implements CloneableTemplate { - constructor(public templateRoot: Element) {} - clone(importIntoDoc: boolean): Node { - if (importIntoDoc) { - return DOM.importIntoDoc(DOM.content(this.templateRoot)); - } else { - return DOM.clone(DOM.content(this.templateRoot)); - } - } -} - -export function prepareTemplateForClone(templateRoot: Element): CloneableTemplate { - var root = DOM.content(templateRoot); - var elementCount = DOM.querySelectorAll(root, '*').length; - var firstChild = DOM.firstChild(root); - var forceSerialize = - isPresent(firstChild) && DOM.isCommentNode(firstChild) ? DOM.nodeValue(firstChild) : null; - if (forceSerialize == 'nocache') { - return new SerializedCloneableTemplate(templateRoot); - } else if (forceSerialize == 'cache') { - return new ReferenceCloneableTemplate(templateRoot); - } else if (elementCount > MAX_IN_MEMORY_ELEMENTS_PER_TEMPLATE) { - return new SerializedCloneableTemplate(templateRoot); - } else { - return new ReferenceCloneableTemplate(templateRoot); - } -} \ No newline at end of file diff --git a/modules/angular2/src/render/dom/view/proto_view.ts b/modules/angular2/src/render/dom/view/proto_view.ts index 5ac3c7a99a2113..7d1d36e6bdbea3 100644 --- a/modules/angular2/src/render/dom/view/proto_view.ts +++ b/modules/angular2/src/render/dom/view/proto_view.ts @@ -5,7 +5,7 @@ import {RenderProtoViewRef, ViewType, ViewEncapsulation} from '../../api'; import {DOM} from 'angular2/src/dom/dom_adapter'; -import {prepareTemplateForClone, CloneableTemplate} from '../util'; +import {TemplateCloner} from '../template_cloner'; export function resolveInternalDomProtoView(protoViewRef: RenderProtoViewRef): DomProtoView { return (protoViewRef)._protoView; @@ -16,9 +16,9 @@ export class DomProtoViewRef extends RenderProtoViewRef { } export class DomProtoView { - static create(type: ViewType, rootElement: Element, viewEncapsulation: ViewEncapsulation, - fragmentsRootNodeCount: number[], rootTextNodeIndices: number[], - elementBinders: List, + static create(templateCloner: TemplateCloner, type: ViewType, rootElement: Element, + viewEncapsulation: ViewEncapsulation, fragmentsRootNodeCount: number[], + rootTextNodeIndices: number[], elementBinders: List, hostAttributes: Map): DomProtoView { var boundTextNodeCount = rootTextNodeIndices.length; for (var i = 0; i < elementBinders.length; i++) { @@ -27,12 +27,12 @@ export class DomProtoView { var isSingleElementFragment = fragmentsRootNodeCount.length === 1 && fragmentsRootNodeCount[0] === 1 && DOM.isElementNode(DOM.firstChild(DOM.content(rootElement))); - return new DomProtoView(type, prepareTemplateForClone(rootElement), viewEncapsulation, + return new DomProtoView(type, templateCloner.prepareForClone(rootElement), viewEncapsulation, elementBinders, hostAttributes, rootTextNodeIndices, boundTextNodeCount, fragmentsRootNodeCount, isSingleElementFragment); } - - constructor(public type: ViewType, public cloneableTemplate: CloneableTemplate, + // Note: fragments are separated by a comment node that is not counted in fragmentsRootNodeCount! + constructor(public type: ViewType, public cloneableTemplate: Element | string, public encapsulation: ViewEncapsulation, public elementBinders: List, public hostAttributes: Map, public rootTextNodeIndices: number[], diff --git a/modules/angular2/src/render/dom/view/proto_view_builder.ts b/modules/angular2/src/render/dom/view/proto_view_builder.ts index 8194a64914be1e..11fdc653f334d3 100644 --- a/modules/angular2/src/render/dom/view/proto_view_builder.ts +++ b/modules/angular2/src/render/dom/view/proto_view_builder.ts @@ -21,6 +21,7 @@ import { import {DomProtoView, DomProtoViewRef, resolveInternalDomProtoView} from './proto_view'; import {DomElementBinder, Event, HostAction} from './element_binder'; import {ElementSchemaRegistry} from '../schema/element_schema_registry'; +import {TemplateCloner} from '../template_cloner'; import * as api from '../../api'; @@ -69,7 +70,7 @@ export class ProtoViewBuilder { setHostAttribute(name: string, value: string) { this.hostAttributes.set(name, value); } - build(schemaRegistry: ElementSchemaRegistry): api.ProtoViewDto { + build(schemaRegistry: ElementSchemaRegistry, templateCloner: TemplateCloner): api.ProtoViewDto { var domElementBinders = []; var apiElementBinders = []; @@ -96,8 +97,9 @@ export class ProtoViewBuilder { dbb.hostPropertyBindings, null) }); }); - var nestedProtoView = - isPresent(ebb.nestedProtoView) ? ebb.nestedProtoView.build(schemaRegistry) : null; + var nestedProtoView = isPresent(ebb.nestedProtoView) ? + ebb.nestedProtoView.build(schemaRegistry, templateCloner) : + null; if (isPresent(nestedProtoView)) { transitiveNgContentCount += nestedProtoView.transitiveNgContentCount; } @@ -131,9 +133,9 @@ export class ProtoViewBuilder { }); var rootNodeCount = DOM.childNodes(DOM.content(this.rootElement)).length; return new api.ProtoViewDto({ - render: new DomProtoViewRef( - DomProtoView.create(this.type, this.rootElement, this.viewEncapsulation, [rootNodeCount], - rootTextNodeIndices, domElementBinders, this.hostAttributes)), + render: new DomProtoViewRef(DomProtoView.create( + templateCloner, this.type, this.rootElement, this.viewEncapsulation, [rootNodeCount], + rootTextNodeIndices, domElementBinders, this.hostAttributes)), type: this.type, elementBinders: apiElementBinders, variableBindings: this.variableBindings, diff --git a/modules/angular2/src/render/dom/view/proto_view_merger.ts b/modules/angular2/src/render/dom/view/proto_view_merger.ts index 6a213c9ab21646..3648b61d87f3c2 100644 --- a/modules/angular2/src/render/dom/view/proto_view_merger.ts +++ b/modules/angular2/src/render/dom/view/proto_view_merger.ts @@ -22,12 +22,15 @@ import { prependAll } from '../util'; -export function mergeProtoViewsRecursively(protoViewRefs: List>): +import {TemplateCloner} from '../template_cloner'; + +export function mergeProtoViewsRecursively(templateCloner: TemplateCloner, + protoViewRefs: List>): RenderProtoViewMergeMapping { // clone var clonedProtoViews = []; var hostViewAndBinderIndices: number[][] = []; - cloneProtoViews(protoViewRefs, clonedProtoViews, hostViewAndBinderIndices); + cloneProtoViews(templateCloner, protoViewRefs, clonedProtoViews, hostViewAndBinderIndices); var mainProtoView: ClonedProtoView = clonedProtoViews[0]; // modify the DOM @@ -41,8 +44,8 @@ export function mergeProtoViewsRecursively(protoViewRefs: List fragment.length); + var rootElement = createRootElementFromFragments(fragments); var rootNode = DOM.content(rootElement); // read out the new element / text node / ElementBinder order @@ -63,21 +66,22 @@ export function mergeProtoViewsRecursively(protoViewRefs: List>, - targetClonedProtoViews: ClonedProtoView[], - targetHostViewAndBinderIndices: number[][]) { +function cloneProtoViews( + templateCloner: TemplateCloner, protoViewRefs: List>, + targetClonedProtoViews: ClonedProtoView[], targetHostViewAndBinderIndices: number[][]) { var hostProtoView = resolveInternalDomProtoView(protoViewRefs[0]); var hostPvIdx = targetClonedProtoViews.length; - targetClonedProtoViews.push(cloneAndQueryProtoView(hostProtoView, false)); + targetClonedProtoViews.push(cloneAndQueryProtoView(templateCloner, hostProtoView, false)); if (targetHostViewAndBinderIndices.length === 0) { targetHostViewAndBinderIndices.push([null, null]); } @@ -89,11 +93,11 @@ function cloneProtoViews(protoViewRefs: List>, if (isPresent(nestedEntry)) { targetHostViewAndBinderIndices.push([hostPvIdx, i]); if (isArray(nestedEntry)) { - cloneProtoViews(nestedEntry, targetClonedProtoViews, + cloneProtoViews(templateCloner, nestedEntry, targetClonedProtoViews, targetHostViewAndBinderIndices); } else { - targetClonedProtoViews.push( - cloneAndQueryProtoView(resolveInternalDomProtoView(nestedEntry), false)); + targetClonedProtoViews.push(cloneAndQueryProtoView( + templateCloner, resolveInternalDomProtoView(nestedEntry), false)); } } } @@ -302,8 +306,16 @@ function sortContentElements(contentElements: Element[]): Element[] { function createRootElementFromFragments(fragments: Node[][]): Element { var rootElement = DOM.createTemplate(''); var rootNode = DOM.content(rootElement); - fragments.forEach( - (fragment) => { fragment.forEach((node) => { DOM.appendChild(rootNode, node); }); }); + for (var i = 0; i < fragments.length; i++) { + var fragment = fragments[i]; + if (i >= 1) { + // Note: We need to seprate fragments by a comment so that sibling + // text nodes don't get merged when we serialize the DomProtoView into a string + // and parse it back again. + DOM.appendChild(rootNode, DOM.createComment('|')); + } + fragment.forEach((node) => { DOM.appendChild(rootNode, node); }); + } return rootElement; } diff --git a/modules/angular2/src/render/render.ts b/modules/angular2/src/render/render.ts index 6aeb4ca9561b99..f15db9027c7d3e 100644 --- a/modules/angular2/src/render/render.ts +++ b/modules/angular2/src/render/render.ts @@ -9,4 +9,5 @@ export * from './dom/view/shared_styles_host'; export * from './dom/compiler/compiler'; export * from './dom/dom_renderer'; export * from './dom/dom_tokens'; +export * from './dom/template_cloner'; export * from './api'; diff --git a/modules/angular2/src/test_lib/test_injector.ts b/modules/angular2/src/test_lib/test_injector.ts index f6dc0cf1d78f22..d7c4a9ea2e2c14 100644 --- a/modules/angular2/src/test_lib/test_injector.ts +++ b/modules/angular2/src/test_lib/test_injector.ts @@ -54,7 +54,9 @@ import { DefaultDomCompiler, APP_ID_TOKEN, SharedStylesHost, - DomSharedStylesHost + DomSharedStylesHost, + MAX_IN_MEMORY_ELEMENTS_PER_TEMPLATE_TOKEN, + TemplateCloner } from 'angular2/src/render/render'; import {ElementSchemaRegistry} from 'angular2/src/render/dom/schema/element_schema_registry'; import {DomElementSchemaRegistry} from 'angular2/src/render/dom/schema/dom_element_schema_registry'; @@ -98,6 +100,8 @@ function _getAppBindings() { DomRenderer, bind(Renderer).toAlias(DomRenderer), bind(APP_ID_TOKEN).toValue('a'), + TemplateCloner, + bind(MAX_IN_MEMORY_ELEMENTS_PER_TEMPLATE_TOKEN).toValue(-1), DefaultDomCompiler, bind(RenderCompiler).toAlias(DefaultDomCompiler), bind(ElementSchemaRegistry).toValue(new DomElementSchemaRegistry()), diff --git a/modules/angular2/src/transform/template_compiler/generator.dart b/modules/angular2/src/transform/template_compiler/generator.dart index fb0e17fd192c0a..62482989819474 100644 --- a/modules/angular2/src/transform/template_compiler/generator.dart +++ b/modules/angular2/src/transform/template_compiler/generator.dart @@ -13,6 +13,7 @@ import 'package:angular2/src/render/dom/compiler/style_url_resolver.dart'; import 'package:angular2/src/render/dom/compiler/view_loader.dart'; import 'package:angular2/src/render/dom/schema/element_schema_registry.dart'; import 'package:angular2/src/render/dom/schema/dom_element_schema_registry.dart'; +import 'package:angular2/src/render/dom/template_cloner.dart'; import 'package:angular2/src/render/xhr.dart' show XHR; import 'package:angular2/src/reflection/reflection.dart'; import 'package:angular2/src/services/url_resolver.dart'; @@ -35,8 +36,11 @@ Future processTemplates(AssetReader reader, AssetId entryPoint, {bool generateRegistrations: true, bool generateChangeDetectors: true}) async { var viewDefResults = await createViewDefinitions(reader, entryPoint); + // Note: TemplateCloner(-1) never serializes Nodes into strings. + // we might want to change this to TemplateCloner(0) to force the serialization + // later when the transformer also stores the proto view template. var extractor = new _TemplateExtractor( - new DomElementSchemaRegistry(), new XhrImpl(reader, entryPoint)); + new DomElementSchemaRegistry(), new TemplateCloner(-1), new XhrImpl(reader, entryPoint)); var registrations = new reg.Codegen(); var changeDetectorClasses = new change.Codegen(); @@ -87,8 +91,9 @@ class _TemplateExtractor { final CompileStepFactory _factory; ViewLoader _loader; ElementSchemaRegistry _schemaRegistry; + TemplateCloner _templateCloner; - _TemplateExtractor(ElementSchemaRegistry schemaRegistry, XHR xhr) + _TemplateExtractor(ElementSchemaRegistry schemaRegistry, TemplateCloner templateCloner, XHR xhr) : _factory = new CompileStepFactory(new ng.Parser(new ng.Lexer())) { var urlResolver = new UrlResolver(); var styleUrlResolver = new StyleUrlResolver(urlResolver); @@ -96,6 +101,7 @@ class _TemplateExtractor { _loader = new ViewLoader(xhr, styleInliner, styleUrlResolver); _schemaRegistry = schemaRegistry; + _templateCloner = templateCloner; } Future<_ExtractResult> extractTemplates(ViewDefinition viewDef) async { @@ -117,7 +123,7 @@ class _TemplateExtractor { DOM.createTemplate(templateAndStyles.template), ViewType.COMPONENT, viewDef); var protoViewDto = - compileElements[0].inheritedProtoView.build(_schemaRegistry); + compileElements[0].inheritedProtoView.build(_schemaRegistry, _templateCloner); reflector.reflectionCapabilities = savedReflectionCapabilities; diff --git a/modules/angular2/src/web-workers/ui/di_bindings.ts b/modules/angular2/src/web-workers/ui/di_bindings.ts index ec7199df334d4c..f00514c71a27ae 100644 --- a/modules/angular2/src/web-workers/ui/di_bindings.ts +++ b/modules/angular2/src/web-workers/ui/di_bindings.ts @@ -27,7 +27,9 @@ import { DOCUMENT_TOKEN, DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES, DefaultDomCompiler, - APP_ID_RANDOM_BINDING + APP_ID_RANDOM_BINDING, + MAX_IN_MEMORY_ELEMENTS_PER_TEMPLATE_TOKEN, + TemplateCloner } from 'angular2/src/render/render'; import {ElementSchemaRegistry} from 'angular2/src/render/dom/schema/element_schema_registry'; import {DomElementSchemaRegistry} from 'angular2/src/render/dom/schema/dom_element_schema_registry'; @@ -92,6 +94,8 @@ function _injectorBindings(): List> { DomRenderer, bind(Renderer).toAlias(DomRenderer), APP_ID_RANDOM_BINDING, + TemplateCloner, + bind(MAX_IN_MEMORY_ELEMENTS_PER_TEMPLATE_TOKEN).toValue(20), DefaultDomCompiler, bind(RenderCompiler).toAlias(DefaultDomCompiler), DomSharedStylesHost, diff --git a/modules/angular2/test/core/compiler/projection_integration_spec.ts b/modules/angular2/test/core/compiler/projection_integration_spec.ts index f1b0a013754f9a..922ee3b79b9eb6 100644 --- a/modules/angular2/test/core/compiler/projection_integration_spec.ts +++ b/modules/angular2/test/core/compiler/projection_integration_spec.ts @@ -38,6 +38,8 @@ import { ViewEncapsulation } from 'angular2/angular2'; +import {MAX_IN_MEMORY_ELEMENTS_PER_TEMPLATE_TOKEN} from 'angular2/src/render/render'; + export function main() { describe('projection', () => { it('should support simple components', @@ -416,6 +418,44 @@ export function main() { })); } + describe('different proto view storages', () => { + function runTests() { + it('should support nested conditionals that contain ng-contents', + inject( + [TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => { + tcb.overrideView(MainComp, new viewAnn.View({ + template: `a`, + directives: [ConditionalTextComponent] + })) + .createAsync(MainComp) + .then((main) => { + expect(main.nativeElement).toHaveText('MAIN()'); + + var viewportElement = main.componentViewChildren[0].componentViewChildren[0]; + viewportElement.inject(ManualViewportDirective).show(); + expect(main.nativeElement).toHaveText('MAIN(FIRST())'); + + viewportElement = main.componentViewChildren[0].componentViewChildren[1]; + viewportElement.inject(ManualViewportDirective).show(); + expect(main.nativeElement).toHaveText('MAIN(FIRST(SECOND(a)))'); + + async.done(); + }); + })); + } + + describe('serialize templates', () => { + beforeEachBindings(() => [bind(MAX_IN_MEMORY_ELEMENTS_PER_TEMPLATE_TOKEN).toValue(0)]); + runTests(); + }); + + describe("don't serialize templates", () => { + beforeEachBindings(() => [bind(MAX_IN_MEMORY_ELEMENTS_PER_TEMPLATE_TOKEN).toValue(-1)]); + runTests(); + }); + + }); + }); } @@ -508,6 +548,15 @@ class InnerInnerComponent { class ConditionalContentComponent { } +@Component({selector: 'conditional-text'}) +@View({ + template: + 'MAIN()', + directives: [ManualViewportDirective] +}) +class ConditionalTextComponent { +} + @Component({selector: 'tab'}) @View({ template: '
TAB()
', diff --git a/modules/angular2/test/render/dom/compiler/compiler_common_tests.ts b/modules/angular2/test/render/dom/compiler/compiler_common_tests.ts index de771c42ecd433..68c92291f9e5c3 100644 --- a/modules/angular2/test/render/dom/compiler/compiler_common_tests.ts +++ b/modules/angular2/test/render/dom/compiler/compiler_common_tests.ts @@ -31,11 +31,10 @@ import {ViewLoader, TemplateAndStyles} from 'angular2/src/render/dom/compiler/vi import {resolveInternalDomProtoView} from 'angular2/src/render/dom/view/proto_view'; import {SharedStylesHost} from 'angular2/src/render/dom/view/shared_styles_host'; +import {TemplateCloner} from 'angular2/src/render/dom/template_cloner'; import {MockStep} from './pipeline_spec'; -import {ReferenceCloneableTemplate} from 'angular2/src/render/dom/util'; - export function runCompilerCommonTests() { describe('DomCompiler', function() { var mockStepFactory: MockStepFactory; @@ -51,8 +50,8 @@ export function runCompilerCommonTests() { var tplLoader = new FakeViewLoader(urlData); mockStepFactory = new MockStepFactory([new MockStep(processElementClosure, processStyleClosure)]); - return new DomCompiler(new ElementSchemaRegistry(), mockStepFactory, tplLoader, - sharedStylesHost); + return new DomCompiler(new ElementSchemaRegistry(), new TemplateCloner(-1), mockStepFactory, + tplLoader, sharedStylesHost); } describe('compile', () => { @@ -255,9 +254,9 @@ export function runCompilerCommonTests() { }); } -function templateRoot(protoViewDto: ProtoViewDto) { +function templateRoot(protoViewDto: ProtoViewDto): Element { var pv = resolveInternalDomProtoView(protoViewDto.render); - return (pv.cloneableTemplate).templateRoot; + return (pv.cloneableTemplate); } class MockStepFactory extends CompileStepFactory { diff --git a/modules/angular2/test/render/dom/template_cloner_spec.ts b/modules/angular2/test/render/dom/template_cloner_spec.ts new file mode 100644 index 00000000000000..d8ac1aabccf52a --- /dev/null +++ b/modules/angular2/test/render/dom/template_cloner_spec.ts @@ -0,0 +1,67 @@ +import { + AsyncTestCompleter, + beforeEach, + ddescribe, + describe, + el, + expect, + iit, + inject, + it, + xit, + beforeEachBindings, + SpyObject, +} from 'angular2/test_lib'; + +import {DOM} from 'angular2/src/dom/dom_adapter'; +import {TemplateCloner} from 'angular2/src/render/dom/template_cloner'; + +export function main() { + describe('TemplateCloner', () => { + var cloner: TemplateCloner; + var bigTemplate: Element; + var smallTemplate: Element; + + beforeEach(() => { + cloner = new TemplateCloner(1); + bigTemplate = DOM.createTemplate('a
'); + smallTemplate = DOM.createTemplate('a'); + }); + + describe('prepareForClone', () => { + it('should use a reference for small templates', + () => { expect(cloner.prepareForClone(smallTemplate)).toBe(smallTemplate); }); + + it('should use a reference if the max element count is -1', () => { + cloner = new TemplateCloner(-1); + expect(cloner.prepareForClone(bigTemplate)).toBe(bigTemplate); + }); + + it('should use a string for big templates', () => { + expect(cloner.prepareForClone(bigTemplate)).toEqual(DOM.getInnerHTML(bigTemplate)); + }); + }); + + describe('cloneTemplate', () => { + + function shouldReturnTemplateContentNodes(template: Element, importIntoDoc: boolean) { + var clone = cloner.cloneContent(cloner.prepareForClone(template), importIntoDoc); + expect(clone).not.toBe(DOM.content(template)); + expect(DOM.getText(DOM.firstChild(clone))).toEqual('a'); + } + + it('should return template.content nodes (small template, no import)', + () => { shouldReturnTemplateContentNodes(smallTemplate, false); }); + + it('should return template.content nodes (small template, import)', + () => { shouldReturnTemplateContentNodes(smallTemplate, true); }); + + it('should return template.content nodes (big template, no import)', + () => { shouldReturnTemplateContentNodes(bigTemplate, false); }); + + it('should return template.content nodes (big template, import)', + () => { shouldReturnTemplateContentNodes(bigTemplate, true); }); + }); + + }); +} \ No newline at end of file diff --git a/modules/angular2/test/render/dom/util_spec.ts b/modules/angular2/test/render/dom/util_spec.ts deleted file mode 100644 index c621e9688ad258..00000000000000 --- a/modules/angular2/test/render/dom/util_spec.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { - AsyncTestCompleter, - beforeEach, - ddescribe, - describe, - el, - expect, - iit, - inject, - it, - xit, - beforeEachBindings, - SpyObject, -} from 'angular2/test_lib'; - -import {DOM} from 'angular2/src/dom/dom_adapter'; -import { - prepareTemplateForClone, - ReferenceCloneableTemplate, - SerializedCloneableTemplate -} from 'angular2/src/render/dom/util'; - -export function main() { - describe('Dom util', () => { - - describe('prepareTemplateForClone', () => { - it('should use a reference for small templates', () => { - var t = DOM.createTemplate(''); - var ct = prepareTemplateForClone(t); - expect((ct).templateRoot).toBe(t); - }); - - it('should use a reference for big templates with a force comment', () => { - var templateString = ''; - for (var i = 0; i < 100; i++) { - templateString += '
'; - } - var t = DOM.createTemplate(templateString); - var ct = prepareTemplateForClone(t); - expect((ct).templateRoot).toBe(t); - }); - - it('should serialize for big templates', () => { - var templateString = ''; - for (var i = 0; i < 100; i++) { - templateString += '
'; - } - var t = DOM.createTemplate(templateString); - var ct = prepareTemplateForClone(t); - expect((ct).templateString).toEqual(templateString); - }); - - it('should serialize for templates with the force comment', () => { - var templateString = ''; - var t = DOM.createTemplate(templateString); - var ct = prepareTemplateForClone(t); - expect((ct).templateString).toEqual(templateString); - }); - }); - - describe('ReferenceCloneableTemplate', () => { - it('should return template.content nodes (no import)', () => { - var t = DOM.createTemplate('a'); - var ct = new ReferenceCloneableTemplate(t); - var clone = ct.clone(false); - expect(clone).not.toBe(DOM.content(t)); - expect(DOM.getText(DOM.firstChild(clone))).toEqual('a'); - }); - - it('should return template.content nodes (import into doc)', () => { - var t = DOM.createTemplate('a'); - var ct = new ReferenceCloneableTemplate(t); - var clone = ct.clone(true); - expect(clone).not.toBe(DOM.content(t)); - expect(DOM.getText(DOM.firstChild(clone))).toEqual('a'); - }); - }); - - describe('SerializedCloneableTemplate', () => { - it('should return template.content nodes (no import)', () => { - var t = DOM.createTemplate('a'); - var ct = new SerializedCloneableTemplate(t); - var clone = ct.clone(false); - expect(clone).not.toBe(DOM.content(t)); - expect(DOM.getText(DOM.firstChild(clone))).toEqual('a'); - }); - - it('should return template.content nodes (import into doc)', () => { - var t = DOM.createTemplate('a'); - var ct = new SerializedCloneableTemplate(t); - var clone = ct.clone(true); - expect(clone).not.toBe(DOM.content(t)); - expect(DOM.getText(DOM.firstChild(clone))).toEqual('a'); - }); - }); - }); -} \ No newline at end of file diff --git a/modules/angular2/test/render/dom/view/proto_view_builder_spec.ts b/modules/angular2/test/render/dom/view/proto_view_builder_spec.ts index 3c4b2642faa331..d21e945c1f8a48 100644 --- a/modules/angular2/test/render/dom/view/proto_view_builder_spec.ts +++ b/modules/angular2/test/render/dom/view/proto_view_builder_spec.ts @@ -12,6 +12,7 @@ import { } from 'angular2/test_lib'; import {DomElementSchemaRegistry} from 'angular2/src/render/dom/schema/dom_element_schema_registry'; +import {TemplateCloner} from 'angular2/src/render/dom/template_cloner'; import {ProtoViewBuilder} from 'angular2/src/render/dom/view/proto_view_builder'; import {ASTWithSource, AST} from 'angular2/src/change_detection/change_detection'; import {PropertyBindingType, ViewType, ViewEncapsulation} from 'angular2/src/render/api'; @@ -22,7 +23,9 @@ export function main() { describe('ProtoViewBuilder', () => { var builder; + var templateCloner; beforeEach(() => { + templateCloner = new TemplateCloner(-1); builder = new ProtoViewBuilder(DOM.createTemplate(''), ViewType.EMBEDDED, ViewEncapsulation.NONE); }); @@ -32,7 +35,7 @@ export function main() { it('should throw for unknown properties', () => { builder.bindElement(el('
')).bindProperty('unknownProperty', emptyExpr()); - expect(() => builder.build(new DomElementSchemaRegistry())) + expect(() => builder.build(new DomElementSchemaRegistry(), templateCloner)) .toThrowError( `Can't bind to 'unknownProperty' since it isn't a known property of the '
' element and there are no matching directives with a corresponding property`); }); @@ -41,7 +44,7 @@ export function main() { var binder = builder.bindElement(el('
')); binder.bindDirective(0).bindProperty('someDirProperty', emptyExpr(), 'directiveProperty'); binder.bindProperty('directiveProperty', emptyExpr()); - expect(() => builder.build(new DomElementSchemaRegistry())).not.toThrow(); + expect(() => builder.build(new DomElementSchemaRegistry(), templateCloner)).not.toThrow(); }); it('should throw for unknown host properties even if another directive uses it', () => { @@ -56,14 +59,14 @@ export function main() { it('should allow unknown properties on custom elements', () => { var binder = builder.bindElement(el('')); binder.bindProperty('unknownProperty', emptyExpr()); - expect(() => builder.build(new DomElementSchemaRegistry())).not.toThrow(); + expect(() => builder.build(new DomElementSchemaRegistry(), templateCloner)).not.toThrow(); }); it('should throw for unknown properties on custom elements if there is an ng component', () => { var binder = builder.bindElement(el('')); binder.bindProperty('unknownProperty', emptyExpr()); binder.setComponentId('someComponent'); - expect(() => builder.build(new DomElementSchemaRegistry())) + expect(() => builder.build(new DomElementSchemaRegistry(), templateCloner)) .toThrowError( `Can't bind to 'unknownProperty' since it isn't a known property of the '' element and there are no matching directives with a corresponding property`); }); @@ -77,7 +80,7 @@ export function main() { // when https://github.com/angular/angular/issues/3019 is solved. it('should not throw for unknown properties', () => { builder.bindElement(el('
')).bindProperty('unknownProperty', emptyExpr()); - expect(() => builder.build(new DomElementSchemaRegistry())).not.toThrow(); + expect(() => builder.build(new DomElementSchemaRegistry(), templateCloner)).not.toThrow(); }); }); @@ -86,19 +89,19 @@ export function main() { describe('property normalization', () => { it('should normalize "innerHtml" to "innerHTML"', () => { builder.bindElement(el('
')).bindProperty('innerHtml', emptyExpr()); - var pv = builder.build(new DomElementSchemaRegistry()); + var pv = builder.build(new DomElementSchemaRegistry(), templateCloner); expect(pv.elementBinders[0].propertyBindings[0].property).toEqual('innerHTML'); }); it('should normalize "tabindex" to "tabIndex"', () => { builder.bindElement(el('
')).bindProperty('tabindex', emptyExpr()); - var pv = builder.build(new DomElementSchemaRegistry()); + var pv = builder.build(new DomElementSchemaRegistry(), templateCloner); expect(pv.elementBinders[0].propertyBindings[0].property).toEqual('tabIndex'); }); it('should normalize "readonly" to "readOnly"', () => { builder.bindElement(el('')).bindProperty('readonly', emptyExpr()); - var pv = builder.build(new DomElementSchemaRegistry()); + var pv = builder.build(new DomElementSchemaRegistry(), templateCloner); expect(pv.elementBinders[0].propertyBindings[0].property).toEqual('readOnly'); }); @@ -107,32 +110,32 @@ export function main() { describe('property binding types', () => { it('should detect property names', () => { builder.bindElement(el('
')).bindProperty('tabindex', emptyExpr()); - var pv = builder.build(new DomElementSchemaRegistry()); + var pv = builder.build(new DomElementSchemaRegistry(), templateCloner); expect(pv.elementBinders[0].propertyBindings[0].type).toEqual(PropertyBindingType.PROPERTY); }); it('should detect attribute names', () => { builder.bindElement(el('
')).bindProperty('attr.someName', emptyExpr()); - var pv = builder.build(new DomElementSchemaRegistry()); + var pv = builder.build(new DomElementSchemaRegistry(), templateCloner); expect(pv.elementBinders[0].propertyBindings[0].type) .toEqual(PropertyBindingType.ATTRIBUTE); }); it('should detect class names', () => { builder.bindElement(el('
')).bindProperty('class.someName', emptyExpr()); - var pv = builder.build(new DomElementSchemaRegistry()); + var pv = builder.build(new DomElementSchemaRegistry(), templateCloner); expect(pv.elementBinders[0].propertyBindings[0].type).toEqual(PropertyBindingType.CLASS); }); it('should detect style names', () => { builder.bindElement(el('
')).bindProperty('style.someName', emptyExpr()); - var pv = builder.build(new DomElementSchemaRegistry()); + var pv = builder.build(new DomElementSchemaRegistry(), templateCloner); expect(pv.elementBinders[0].propertyBindings[0].type).toEqual(PropertyBindingType.STYLE); }); it('should detect style units', () => { builder.bindElement(el('
')).bindProperty('style.someName.someUnit', emptyExpr()); - var pv = builder.build(new DomElementSchemaRegistry()); + var pv = builder.build(new DomElementSchemaRegistry(), templateCloner); expect(pv.elementBinders[0].propertyBindings[0].unit).toEqual('someUnit'); }); }); diff --git a/modules/angular2/test/render/dom/view/proto_view_merger_integration_spec.ts b/modules/angular2/test/render/dom/view/proto_view_merger_integration_spec.ts index 9df5c4d8061839..10c76c6f192323 100644 --- a/modules/angular2/test/render/dom/view/proto_view_merger_integration_spec.ts +++ b/modules/angular2/test/render/dom/view/proto_view_merger_integration_spec.ts @@ -28,7 +28,8 @@ import { } from 'angular2/src/render/api'; import {DOM} from 'angular2/src/dom/dom_adapter'; -import {cloneAndQueryProtoView, ReferenceCloneableTemplate} from 'angular2/src/render/dom/util'; +import {cloneAndQueryProtoView} from 'angular2/src/render/dom/util'; +import {TemplateCloner} from 'angular2/src/render/dom/template_cloner'; import {resolveInternalDomProtoView, DomProtoView} from 'angular2/src/render/dom/view/proto_view'; import {ProtoViewBuilder} from 'angular2/src/render/dom/view/proto_view_builder'; import {ElementSchemaRegistry} from 'angular2/src/render/dom/schema/element_schema_registry'; @@ -255,13 +256,14 @@ export function main() { describe('host attributes', () => { it('should set host attributes while merging', - inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => { + inject([AsyncTestCompleter, DomTestbed, TemplateCloner], (async, tb: DomTestbed, + cloner: TemplateCloner) => { tb.compiler.compileHost(rootDirective('root')) .then((rootProtoViewDto) => { var builder = new ProtoViewBuilder(DOM.createTemplate(''), ViewType.COMPONENT, ViewEncapsulation.NONE); builder.setHostAttribute('a', 'b'); - var componentProtoViewDto = builder.build(new ElementSchemaRegistry()); + var componentProtoViewDto = builder.build(new ElementSchemaRegistry(), cloner); tb.merge([rootProtoViewDto, componentProtoViewDto]) .then(mergeMappings => { var domPv = resolveInternalDomProtoView(mergeMappings.mergedProtoViewRef); @@ -278,20 +280,21 @@ export function main() { } function templateRoot(pv: DomProtoView) { - return (pv.cloneableTemplate).templateRoot; + return pv.cloneableTemplate; } function runAndAssert(hostElementName: string, componentTemplates: string[], expectedFragments: string[]) { var useNativeEncapsulation = hostElementName.startsWith('native-'); var rootComp = rootDirective(hostElementName); - return inject([AsyncTestCompleter, DomTestbed], (async, tb: DomTestbed) => { + return inject([AsyncTestCompleter, DomTestbed, TemplateCloner], (async, tb: DomTestbed, + cloner: TemplateCloner) => { tb.compileAndMerge(rootComp, componentTemplates.map(template => componentView( template, useNativeEncapsulation ? ViewEncapsulation.NATIVE : ViewEncapsulation.NONE))) .then((mergeMappings) => { - expect(stringify(mergeMappings)).toEqual(expectedFragments); + expect(stringify(cloner, mergeMappings)).toEqual(expectedFragments); async.done(); }); }); @@ -312,9 +315,10 @@ function componentView(template: string, }); } -function stringify(protoViewMergeMapping: RenderProtoViewMergeMapping): string[] { +function stringify(cloner: TemplateCloner, protoViewMergeMapping: RenderProtoViewMergeMapping): + string[] { var testView = cloneAndQueryProtoView( - resolveInternalDomProtoView(protoViewMergeMapping.mergedProtoViewRef), false); + cloner, resolveInternalDomProtoView(protoViewMergeMapping.mergedProtoViewRef), false); for (var i = 0; i < protoViewMergeMapping.mappedElementIndices.length; i++) { var renderElIdx = protoViewMergeMapping.mappedElementIndices[i]; if (isPresent(renderElIdx)) { diff --git a/modules/angular2/test/render/dom/view/view_spec.ts b/modules/angular2/test/render/dom/view/view_spec.ts index ffd1d8fd5c5795..ed1e71313c068a 100644 --- a/modules/angular2/test/render/dom/view/view_spec.ts +++ b/modules/angular2/test/render/dom/view/view_spec.ts @@ -22,6 +22,7 @@ import {DomProtoView} from 'angular2/src/render/dom/view/proto_view'; import {DomElementBinder} from 'angular2/src/render/dom/view/element_binder'; import {DomView} from 'angular2/src/render/dom/view/view'; import {DOM} from 'angular2/src/dom/dom_adapter'; +import {TemplateCloner} from 'angular2/src/render/dom/template_cloner'; export function main() { describe('DomView', () => { @@ -30,7 +31,8 @@ export function main() { binders = []; } var rootEl = DOM.createTemplate('
'); - return DomProtoView.create(null, rootEl, null, [1], [], binders, null); + return DomProtoView.create(new TemplateCloner(-1), null, rootEl, null, [1], [], + binders, null); } function createElementBinder() { return new DomElementBinder({textNodeIndices: []}); } diff --git a/modules/angular2/test/web-workers/worker/renderer_spec.ts b/modules/angular2/test/web-workers/worker/renderer_spec.ts index fea5f210d5087a..6c8623c10e739d 100644 --- a/modules/angular2/test/web-workers/worker/renderer_spec.ts +++ b/modules/angular2/test/web-workers/worker/renderer_spec.ts @@ -39,7 +39,6 @@ import {someComponent} from '../../render/dom/dom_renderer_integration_spec'; import {WebWorkerMain} from 'angular2/src/web-workers/ui/impl'; import {AnchorBasedAppRootUrl} from 'angular2/src/services/anchor_based_app_root_url'; import {MockMessageBus, MockMessageBusSink, MockMessageBusSource} from './worker_test_util'; -import {ReferenceCloneableTemplate} from 'angular2/src/render/dom/util'; export function main() { function createBroker(workerSerializer: Serializer, uiSerializer: Serializer, tb: DomTestbed, @@ -299,7 +298,7 @@ class WorkerTestRootView extends TestRootView { } function templateRoot(pv: DomProtoView) { - return (pv.cloneableTemplate).templateRoot; + return pv.cloneableTemplate; } function createSerializer(protoViewRefStore: RenderProtoViewRefStore, diff --git a/modules/benchmarks/src/compiler/compiler_benchmark.ts b/modules/benchmarks/src/compiler/compiler_benchmark.ts index f8b0e71a4ce323..90b3f079d54657 100644 --- a/modules/benchmarks/src/compiler/compiler_benchmark.ts +++ b/modules/benchmarks/src/compiler/compiler_benchmark.ts @@ -24,7 +24,12 @@ import {ReflectionCapabilities} from 'angular2/src/reflection/reflection_capabil import {getIntParameter, bindAction} from 'angular2/src/test_lib/benchmark_util'; import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory'; -import {ViewLoader, DefaultDomCompiler, SharedStylesHost} from 'angular2/src/render/render'; +import { + ViewLoader, + DefaultDomCompiler, + SharedStylesHost, + TemplateCloner +} from 'angular2/src/render/render'; import {DomElementSchemaRegistry} from 'angular2/src/render/dom/schema/dom_element_schema_registry'; export function main() { @@ -38,9 +43,9 @@ export function main() { count, [BenchmarkComponentNoBindings, BenchmarkComponentWithBindings]); var urlResolver = new UrlResolver(); var appRootUrl = new AppRootUrl(""); - var renderCompiler = - new DefaultDomCompiler(new DomElementSchemaRegistry(), new Parser(new Lexer()), - new ViewLoader(null, null, null), new SharedStylesHost(), 'a'); + var renderCompiler = new DefaultDomCompiler( + new DomElementSchemaRegistry(), new TemplateCloner(-1), new Parser(new Lexer()), + new ViewLoader(null, null, null), new SharedStylesHost(), 'a'); var compiler = new Compiler(reader, cache, viewResolver, new ComponentUrlMapper(), urlResolver, renderCompiler, new ProtoViewFactory(new DynamicChangeDetection()), appRootUrl);