X Tutup
Skip to content

Commit dd06a87

Browse files
committed
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
1 parent 014b6cb commit dd06a87

File tree

24 files changed

+310
-222
lines changed

24 files changed

+310
-222
lines changed

modules/angular2/render.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@ export {
1717
ViewDefinition,
1818
DOCUMENT_TOKEN,
1919
APP_ID_TOKEN,
20-
DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES
20+
DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES,
21+
MAX_IN_MEMORY_ELEMENTS_PER_TEMPLATE_TOKEN
2122
} from './src/render/render';

modules/angular2/src/core/application_common.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ import {
5959
DOCUMENT_TOKEN,
6060
DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES,
6161
DefaultDomCompiler,
62-
APP_ID_RANDOM_BINDING
62+
APP_ID_RANDOM_BINDING,
63+
MAX_IN_MEMORY_ELEMENTS_PER_TEMPLATE_TOKEN,
64+
TemplateCloner
6365
} from 'angular2/src/render/render';
6466
import {ElementSchemaRegistry} from 'angular2/src/render/dom/schema/element_schema_registry';
6567
import {DomElementSchemaRegistry} from 'angular2/src/render/dom/schema/dom_element_schema_registry';
@@ -114,6 +116,8 @@ function _injectorBindings(appComponentType): List<Type | Binding | List<any>> {
114116
DomRenderer,
115117
bind(Renderer).toAlias(DomRenderer),
116118
APP_ID_RANDOM_BINDING,
119+
TemplateCloner,
120+
bind(MAX_IN_MEMORY_ELEMENTS_PER_TEMPLATE_TOKEN).toValue(20),
117121
DefaultDomCompiler,
118122
bind(ElementSchemaRegistry).toValue(new DomElementSchemaRegistry()),
119123
bind(RenderCompiler).toAlias(DefaultDomCompiler),

modules/angular2/src/dom/parse5_adapter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export class Parse5DomAdapter extends DomAdapter {
7777
return res;
7878
}
7979
elementMatches(node, selector: string, matcher = null): boolean {
80-
if (!selector || selector === '*') {
80+
if (this.isElementNode(node) && selector === '*') {
8181
return true;
8282
}
8383
var result = false;

modules/angular2/src/render/dom/compiler/compiler.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {DOCUMENT_TOKEN, APP_ID_TOKEN} from '../dom_tokens';
2424
import {Inject} from 'angular2/di';
2525
import {SharedStylesHost} from '../view/shared_styles_host';
2626
import {prependAll} from '../util';
27+
import {TemplateCloner} from '../template_cloner';
2728

2829
/**
2930
* The compiler loads and translates the html templates of components into
@@ -32,8 +33,8 @@ import {prependAll} from '../util';
3233
*/
3334
export class DomCompiler extends RenderCompiler {
3435
constructor(private _schemaRegistry: ElementSchemaRegistry,
35-
private _stepFactory: CompileStepFactory, private _viewLoader: ViewLoader,
36-
private _sharedStylesHost: SharedStylesHost) {
36+
private _templateCloner: TemplateCloner, private _stepFactory: CompileStepFactory,
37+
private _viewLoader: ViewLoader, private _sharedStylesHost: SharedStylesHost) {
3738
super();
3839
}
3940

@@ -65,7 +66,8 @@ export class DomCompiler extends RenderCompiler {
6566

6667
mergeProtoViewsRecursively(
6768
protoViewRefs: List<RenderProtoViewRef | List<any>>): Promise<RenderProtoViewMergeMapping> {
68-
return PromiseWrapper.resolve(pvm.mergeProtoViewsRecursively(protoViewRefs));
69+
return PromiseWrapper.resolve(
70+
pvm.mergeProtoViewsRecursively(this._templateCloner, protoViewRefs));
6971
}
7072

7173
_compileView(viewDef: ViewDefinition, templateAndStyles: TemplateAndStyles,
@@ -87,7 +89,7 @@ export class DomCompiler extends RenderCompiler {
8789
}
8890

8991
return PromiseWrapper.resolve(
90-
compileElements[0].inheritedProtoView.build(this._schemaRegistry));
92+
compileElements[0].inheritedProtoView.build(this._schemaRegistry, this._templateCloner));
9193
}
9294

9395
_normalizeViewEncapsulationIfThereAreNoStyles(viewDef: ViewDefinition): ViewDefinition {
@@ -108,8 +110,10 @@ export class DomCompiler extends RenderCompiler {
108110

109111
@Injectable()
110112
export class DefaultDomCompiler extends DomCompiler {
111-
constructor(schemaRegistry: ElementSchemaRegistry, parser: Parser, viewLoader: ViewLoader,
112-
sharedStylesHost: SharedStylesHost, @Inject(APP_ID_TOKEN) appId: any) {
113-
super(schemaRegistry, new DefaultStepFactory(parser, appId), viewLoader, sharedStylesHost);
113+
constructor(schemaRegistry: ElementSchemaRegistry, templateCloner: TemplateCloner, parser: Parser,
114+
viewLoader: ViewLoader, sharedStylesHost: SharedStylesHost,
115+
@Inject(APP_ID_TOKEN) appId: any) {
116+
super(schemaRegistry, templateCloner, new DefaultStepFactory(parser, appId), viewLoader,
117+
sharedStylesHost);
114118
}
115119
}

modules/angular2/src/render/dom/dom_renderer.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import {
3232
RenderViewWithFragments
3333
} from '../api';
3434

35+
import {TemplateCloner} from './template_cloner';
36+
3537
import {DOCUMENT_TOKEN, DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES} from './dom_tokens';
3638

3739
const REFLECT_PREFIX: string = 'ng-reflect-';
@@ -41,8 +43,9 @@ export class DomRenderer extends Renderer {
4143
_document;
4244
_reflectPropertiesAsAttributes: boolean;
4345

44-
constructor(public _eventManager: EventManager, private _domSharedStylesHost: DomSharedStylesHost,
45-
@Inject(DOCUMENT_TOKEN) document,
46+
constructor(private _eventManager: EventManager,
47+
private _domSharedStylesHost: DomSharedStylesHost,
48+
private _templateCloner: TemplateCloner, @Inject(DOCUMENT_TOKEN) document,
4649
@Inject(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES) reflectPropertiesAsAttributes:
4750
boolean) {
4851
super();
@@ -206,7 +209,7 @@ export class DomRenderer extends Renderer {
206209
}
207210

208211
_createView(protoView: DomProtoView, inplaceElement: HTMLElement): RenderViewWithFragments {
209-
var clonedProtoView = cloneAndQueryProtoView(protoView, true);
212+
var clonedProtoView = cloneAndQueryProtoView(this._templateCloner, protoView, true);
210213

211214
var boundElements = clonedProtoView.boundElements;
212215

modules/angular2/src/render/dom/dom_tokens.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ export const APP_ID_TOKEN: OpaqueToken = CONST_EXPR(new OpaqueToken('AppId'));
1818
export var APP_ID_RANDOM_BINDING: Binding =
1919
bind(APP_ID_TOKEN).toFactory(() => `${randomChar()}${randomChar()}${randomChar()}`, []);
2020

21+
/**
22+
* Defines when a compiled template should be stored as a string
23+
* rather than keeping its Nodes to preserve memory.
24+
*/
25+
export const MAX_IN_MEMORY_ELEMENTS_PER_TEMPLATE_TOKEN: OpaqueToken =
26+
CONST_EXPR(new OpaqueToken('MaxInMemoryElementsPerTemplate'));
2127

2228
function randomChar(): string {
2329
return StringWrapper.fromCharCode(97 + Math.floor(Math.random() * 25));
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import {isString} from 'angular2/src/facade/lang';
2+
import {Injectable, Inject} from 'angular2/di';
3+
import {DOM} from 'angular2/src/dom/dom_adapter';
4+
import {MAX_IN_MEMORY_ELEMENTS_PER_TEMPLATE_TOKEN} from './dom_tokens';
5+
6+
@Injectable()
7+
export class TemplateCloner {
8+
maxInMemoryElementsPerTemplate: number;
9+
10+
constructor(@Inject(MAX_IN_MEMORY_ELEMENTS_PER_TEMPLATE_TOKEN) maxInMemoryElementsPerTemplate) {
11+
this.maxInMemoryElementsPerTemplate = maxInMemoryElementsPerTemplate;
12+
}
13+
14+
prepareForClone(templateRoot: Element): Element | string {
15+
var elementCount = DOM.querySelectorAll(DOM.content(templateRoot), '*').length;
16+
if (this.maxInMemoryElementsPerTemplate >= 0 &&
17+
elementCount >= this.maxInMemoryElementsPerTemplate) {
18+
return DOM.getInnerHTML(templateRoot);
19+
} else {
20+
return templateRoot;
21+
}
22+
}
23+
24+
cloneContent(preparedTemplateRoot: Element | string, importNode: boolean): Node {
25+
var templateContent;
26+
if (isString(preparedTemplateRoot)) {
27+
templateContent = DOM.content(DOM.createTemplate(preparedTemplateRoot));
28+
if (importNode) {
29+
// Attention: We can't use document.adoptNode here
30+
// as this does NOT wake up custom elements in Chrome 43
31+
// TODO: Use div.innerHTML instead of template.innerHTML when we
32+
// have code to support the various special cases and
33+
// don't use importNode additionally (e.g. for <tr>, svg elements, ...)
34+
// see https://github.com/angular/angular/issues/3364
35+
templateContent = DOM.importIntoDoc(templateContent);
36+
}
37+
} else {
38+
templateContent = DOM.content(<Element>preparedTemplateRoot);
39+
if (importNode) {
40+
templateContent = DOM.importIntoDoc(templateContent);
41+
} else {
42+
templateContent = DOM.clone(templateContent);
43+
}
44+
}
45+
return templateContent;
46+
}
47+
}

modules/angular2/src/render/dom/util.ts

Lines changed: 8 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {DOM} from 'angular2/src/dom/dom_adapter';
33
import {ListWrapper} from 'angular2/src/facade/collection';
44
import {DomProtoView} from './view/proto_view';
55
import {DomElementBinder} from './view/element_binder';
6+
import {TemplateCloner} from './template_cloner';
67

78
export const NG_BINDING_CLASS_SELECTOR = '.ng-binding';
89
export const NG_BINDING_CLASS = 'ng-binding';
@@ -57,9 +58,9 @@ export class ClonedProtoView {
5758
public boundElements: Element[], public boundTextNodes: Node[]) {}
5859
}
5960

60-
export function cloneAndQueryProtoView(pv: DomProtoView, importIntoDocument: boolean):
61-
ClonedProtoView {
62-
var templateContent = pv.cloneableTemplate.clone(importIntoDocument);
61+
export function cloneAndQueryProtoView(templateCloner: TemplateCloner, pv: DomProtoView,
62+
importIntoDocument: boolean): ClonedProtoView {
63+
var templateContent = templateCloner.cloneContent(pv.cloneableTemplate, importIntoDocument);
6364

6465
var boundElements = queryBoundElements(templateContent, pv.isSingleElementFragment);
6566
var boundTextNodes = queryBoundTextNodes(templateContent, pv.rootTextNodeIndices, boundElements,
@@ -77,6 +78,10 @@ function queryFragments(templateContent: Node, fragmentsRootNodeCount: number[])
7778
for (var fragmentIndex = 0; fragmentIndex < fragments.length; fragmentIndex++) {
7879
var fragment = ListWrapper.createFixedSize(fragmentsRootNodeCount[fragmentIndex]);
7980
fragments[fragmentIndex] = fragment;
81+
// Note: the 2nd, 3rd, ... fragments are separated by each other via a '|'
82+
if (fragmentIndex >= 1) {
83+
childNode = DOM.nextSibling(childNode);
84+
}
8085
for (var i = 0; i < fragment.length; i++) {
8186
fragment[i] = childNode;
8287
childNode = DOM.nextSibling(childNode);
@@ -141,45 +146,3 @@ export function prependAll(parentNode: Node, nodes: Node[]) {
141146
lastInsertedNode = node;
142147
});
143148
}
144-
145-
export interface CloneableTemplate { clone(importIntoDoc: boolean): Node; }
146-
147-
export class SerializedCloneableTemplate implements CloneableTemplate {
148-
templateString: string;
149-
constructor(templateRoot: Element) { this.templateString = DOM.getInnerHTML(templateRoot); }
150-
clone(importIntoDoc: boolean): Node {
151-
var result = DOM.content(DOM.createTemplate(this.templateString));
152-
if (importIntoDoc) {
153-
result = DOM.adoptNode(result);
154-
}
155-
return result;
156-
}
157-
}
158-
159-
export class ReferenceCloneableTemplate implements CloneableTemplate {
160-
constructor(public templateRoot: Element) {}
161-
clone(importIntoDoc: boolean): Node {
162-
if (importIntoDoc) {
163-
return DOM.importIntoDoc(DOM.content(this.templateRoot));
164-
} else {
165-
return DOM.clone(DOM.content(this.templateRoot));
166-
}
167-
}
168-
}
169-
170-
export function prepareTemplateForClone(templateRoot: Element): CloneableTemplate {
171-
var root = DOM.content(templateRoot);
172-
var elementCount = DOM.querySelectorAll(root, '*').length;
173-
var firstChild = DOM.firstChild(root);
174-
var forceSerialize =
175-
isPresent(firstChild) && DOM.isCommentNode(firstChild) ? DOM.nodeValue(firstChild) : null;
176-
if (forceSerialize == 'nocache') {
177-
return new SerializedCloneableTemplate(templateRoot);
178-
} else if (forceSerialize == 'cache') {
179-
return new ReferenceCloneableTemplate(templateRoot);
180-
} else if (elementCount > MAX_IN_MEMORY_ELEMENTS_PER_TEMPLATE) {
181-
return new SerializedCloneableTemplate(templateRoot);
182-
} else {
183-
return new ReferenceCloneableTemplate(templateRoot);
184-
}
185-
}

modules/angular2/src/render/dom/view/proto_view.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {RenderProtoViewRef, ViewType, ViewEncapsulation} from '../../api';
55

66
import {DOM} from 'angular2/src/dom/dom_adapter';
77

8-
import {prepareTemplateForClone, CloneableTemplate} from '../util';
8+
import {TemplateCloner} from '../template_cloner';
99

1010
export function resolveInternalDomProtoView(protoViewRef: RenderProtoViewRef): DomProtoView {
1111
return (<DomProtoViewRef>protoViewRef)._protoView;
@@ -16,9 +16,9 @@ export class DomProtoViewRef extends RenderProtoViewRef {
1616
}
1717

1818
export class DomProtoView {
19-
static create(type: ViewType, rootElement: Element, viewEncapsulation: ViewEncapsulation,
20-
fragmentsRootNodeCount: number[], rootTextNodeIndices: number[],
21-
elementBinders: List<DomElementBinder>,
19+
static create(templateCloner: TemplateCloner, type: ViewType, rootElement: Element,
20+
viewEncapsulation: ViewEncapsulation, fragmentsRootNodeCount: number[],
21+
rootTextNodeIndices: number[], elementBinders: List<DomElementBinder>,
2222
hostAttributes: Map<string, string>): DomProtoView {
2323
var boundTextNodeCount = rootTextNodeIndices.length;
2424
for (var i = 0; i < elementBinders.length; i++) {
@@ -27,12 +27,12 @@ export class DomProtoView {
2727
var isSingleElementFragment = fragmentsRootNodeCount.length === 1 &&
2828
fragmentsRootNodeCount[0] === 1 &&
2929
DOM.isElementNode(DOM.firstChild(DOM.content(rootElement)));
30-
return new DomProtoView(type, prepareTemplateForClone(rootElement), viewEncapsulation,
30+
return new DomProtoView(type, templateCloner.prepareForClone(rootElement), viewEncapsulation,
3131
elementBinders, hostAttributes, rootTextNodeIndices, boundTextNodeCount,
3232
fragmentsRootNodeCount, isSingleElementFragment);
3333
}
34-
35-
constructor(public type: ViewType, public cloneableTemplate: CloneableTemplate,
34+
// Note: fragments are separated by a comment node that is not counted in fragmentsRootNodeCount!
35+
constructor(public type: ViewType, public cloneableTemplate: Element | string,
3636
public encapsulation: ViewEncapsulation,
3737
public elementBinders: List<DomElementBinder>,
3838
public hostAttributes: Map<string, string>, public rootTextNodeIndices: number[],

modules/angular2/src/render/dom/view/proto_view_builder.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
import {DomProtoView, DomProtoViewRef, resolveInternalDomProtoView} from './proto_view';
2222
import {DomElementBinder, Event, HostAction} from './element_binder';
2323
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
24+
import {TemplateCloner} from '../template_cloner';
2425

2526
import * as api from '../../api';
2627

@@ -69,7 +70,7 @@ export class ProtoViewBuilder {
6970

7071
setHostAttribute(name: string, value: string) { this.hostAttributes.set(name, value); }
7172

72-
build(schemaRegistry: ElementSchemaRegistry): api.ProtoViewDto {
73+
build(schemaRegistry: ElementSchemaRegistry, templateCloner: TemplateCloner): api.ProtoViewDto {
7374
var domElementBinders = [];
7475

7576
var apiElementBinders = [];
@@ -96,8 +97,9 @@ export class ProtoViewBuilder {
9697
dbb.hostPropertyBindings, null)
9798
});
9899
});
99-
var nestedProtoView =
100-
isPresent(ebb.nestedProtoView) ? ebb.nestedProtoView.build(schemaRegistry) : null;
100+
var nestedProtoView = isPresent(ebb.nestedProtoView) ?
101+
ebb.nestedProtoView.build(schemaRegistry, templateCloner) :
102+
null;
101103
if (isPresent(nestedProtoView)) {
102104
transitiveNgContentCount += nestedProtoView.transitiveNgContentCount;
103105
}
@@ -131,9 +133,9 @@ export class ProtoViewBuilder {
131133
});
132134
var rootNodeCount = DOM.childNodes(DOM.content(this.rootElement)).length;
133135
return new api.ProtoViewDto({
134-
render: new DomProtoViewRef(
135-
DomProtoView.create(this.type, this.rootElement, this.viewEncapsulation, [rootNodeCount],
136-
rootTextNodeIndices, domElementBinders, this.hostAttributes)),
136+
render: new DomProtoViewRef(DomProtoView.create(
137+
templateCloner, this.type, this.rootElement, this.viewEncapsulation, [rootNodeCount],
138+
rootTextNodeIndices, domElementBinders, this.hostAttributes)),
137139
type: this.type,
138140
elementBinders: apiElementBinders,
139141
variableBindings: this.variableBindings,

0 commit comments

Comments
 (0)
X Tutup