diff --git a/packages/core/src/render3/hooks.ts b/packages/core/src/render3/hooks.ts index 8f7c1a4945c39..4c7c5641181fe 100644 --- a/packages/core/src/render3/hooks.ts +++ b/packages/core/src/render3/hooks.ts @@ -8,6 +8,7 @@ import {assertEqual, assertNotEqual} from '../util/assert'; +import {assertFirstTemplatePass} from './assert'; import {DirectiveDef} from './interfaces/definition'; import {TNode} from './interfaces/node'; import {FLAGS, HookData, InitPhaseState, LView, LViewFlags, PREORDER_HOOK_FLAGS, PreOrderHookFlags, TView} from './interfaces/view'; @@ -35,9 +36,7 @@ import {getCheckNoChangesMode} from './state'; export function registerPreOrderHooks( directiveIndex: number, directiveDef: DirectiveDef, tView: TView, nodeIndex: number, initialPreOrderHooksLength: number, initialPreOrderCheckHooksLength: number): void { - ngDevMode && - assertEqual(tView.firstTemplatePass, true, 'Should only be called on first template pass'); - + ngDevMode && assertFirstTemplatePass(tView); const {onChanges, onInit, doCheck} = directiveDef; if (initialPreOrderHooksLength >= 0 && (!tView.preOrderHooks || initialPreOrderHooksLength === tView.preOrderHooks.length) && @@ -86,35 +85,33 @@ export function registerPreOrderHooks( * @param tNode The TNode whose directives are to be searched for hooks to queue */ export function registerPostOrderHooks(tView: TView, tNode: TNode): void { - if (tView.firstTemplatePass) { - // It's necessary to loop through the directives at elementEnd() (rather than processing in - // directiveCreate) so we can preserve the current hook order. Content, view, and destroy - // hooks for projected components and directives must be called *before* their hosts. - for (let i = tNode.directiveStart, end = tNode.directiveEnd; i < end; i++) { - const directiveDef = tView.data[i] as DirectiveDef; - if (directiveDef.afterContentInit) { - (tView.contentHooks || (tView.contentHooks = [])).push(-i, directiveDef.afterContentInit); - } + ngDevMode && assertFirstTemplatePass(tView); + // It's necessary to loop through the directives at elementEnd() (rather than processing in + // directiveCreate) so we can preserve the current hook order. Content, view, and destroy + // hooks for projected components and directives must be called *before* their hosts. + for (let i = tNode.directiveStart, end = tNode.directiveEnd; i < end; i++) { + const directiveDef = tView.data[i] as DirectiveDef; + if (directiveDef.afterContentInit) { + (tView.contentHooks || (tView.contentHooks = [])).push(-i, directiveDef.afterContentInit); + } - if (directiveDef.afterContentChecked) { - (tView.contentHooks || (tView.contentHooks = [])).push(i, directiveDef.afterContentChecked); - (tView.contentCheckHooks || (tView.contentCheckHooks = [ - ])).push(i, directiveDef.afterContentChecked); - } + if (directiveDef.afterContentChecked) { + (tView.contentHooks || (tView.contentHooks = [])).push(i, directiveDef.afterContentChecked); + (tView.contentCheckHooks || (tView.contentCheckHooks = [ + ])).push(i, directiveDef.afterContentChecked); + } - if (directiveDef.afterViewInit) { - (tView.viewHooks || (tView.viewHooks = [])).push(-i, directiveDef.afterViewInit); - } + if (directiveDef.afterViewInit) { + (tView.viewHooks || (tView.viewHooks = [])).push(-i, directiveDef.afterViewInit); + } - if (directiveDef.afterViewChecked) { - (tView.viewHooks || (tView.viewHooks = [])).push(i, directiveDef.afterViewChecked); - (tView.viewCheckHooks || (tView.viewCheckHooks = [ - ])).push(i, directiveDef.afterViewChecked); - } + if (directiveDef.afterViewChecked) { + (tView.viewHooks || (tView.viewHooks = [])).push(i, directiveDef.afterViewChecked); + (tView.viewCheckHooks || (tView.viewCheckHooks = [])).push(i, directiveDef.afterViewChecked); + } - if (directiveDef.onDestroy != null) { - (tView.destroyHooks || (tView.destroyHooks = [])).push(i, directiveDef.onDestroy); - } + if (directiveDef.onDestroy != null) { + (tView.destroyHooks || (tView.destroyHooks = [])).push(i, directiveDef.onDestroy); } } } diff --git a/packages/core/src/render3/instructions/container.ts b/packages/core/src/render3/instructions/container.ts index f2e25ac44d929..3684fedae1b9c 100644 --- a/packages/core/src/render3/instructions/container.ts +++ b/packages/core/src/render3/instructions/container.ts @@ -74,6 +74,7 @@ export function ɵɵtemplate( if (tView.firstTemplatePass) { ngDevMode && ngDevMode.firstTemplatePass++; resolveDirectives(tView, lView, tContainerNode, localRefs || null); + registerPostOrderHooks(tView, tContainerNode); const embeddedTView = tContainerNode.tViews = createTView( -1, templateFn, consts, vars, tView.directiveRegistry, tView.pipeRegistry, null, @@ -90,7 +91,6 @@ export function ɵɵtemplate( createDirectivesAndLocals(tView, lView, tContainerNode, localRefExtractor); attachPatchData(getNativeByTNode(tContainerNode, lView), lView); - registerPostOrderHooks(tView, tContainerNode); setIsNotParent(); } diff --git a/packages/core/src/render3/instructions/element.ts b/packages/core/src/render3/instructions/element.ts index 3b6eb3e4116ea..755894002f796 100644 --- a/packages/core/src/render3/instructions/element.ts +++ b/packages/core/src/render3/instructions/element.ts @@ -124,12 +124,13 @@ export function ɵɵelementEnd(): void { const lView = getLView(); const tView = lView[TVIEW]; - registerPostOrderHooks(tView, previousOrParentTNode); decreaseElementDepthCount(); - if (tView.firstTemplatePass && tView.queries !== null && - isContentQueryHost(previousOrParentTNode)) { - tView.queries !.elementEnd(previousOrParentTNode); + if (tView.firstTemplatePass) { + registerPostOrderHooks(tView, previousOrParentTNode); + if (isContentQueryHost(previousOrParentTNode)) { + tView.queries !.elementEnd(previousOrParentTNode); + } } if (hasClassInput(tNode) && tNode.classes) { diff --git a/packages/core/src/render3/instructions/element_container.ts b/packages/core/src/render3/instructions/element_container.ts index 2e31723aa9fea..5c38008487aed 100644 --- a/packages/core/src/render3/instructions/element_container.ts +++ b/packages/core/src/render3/instructions/element_container.ts @@ -94,11 +94,11 @@ export function ɵɵelementContainerEnd(): void { ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.ElementContainer); - registerPostOrderHooks(tView, previousOrParentTNode); - - if (tView.firstTemplatePass && tView.queries !== null && - isContentQueryHost(previousOrParentTNode)) { - tView.queries.elementEnd(previousOrParentTNode); + if (tView.firstTemplatePass) { + registerPostOrderHooks(tView, previousOrParentTNode); + if (isContentQueryHost(previousOrParentTNode)) { + tView.queries !.elementEnd(previousOrParentTNode); + } } } diff --git a/packages/core/src/render3/instructions/text.ts b/packages/core/src/render3/instructions/text.ts index de06ce514b869..a76e4bae18b73 100644 --- a/packages/core/src/render3/instructions/text.ts +++ b/packages/core/src/render3/instructions/text.ts @@ -5,15 +5,13 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import {assertDataInRange, assertDefined, assertEqual} from '../../util/assert'; +import {assertDataInRange, assertEqual} from '../../util/assert'; import {TNodeType} from '../interfaces/node'; -import {RText, isProceduralRenderer} from '../interfaces/renderer'; -import {BINDING_INDEX, HEADER_OFFSET, LView, RENDERER, TVIEW, T_HOST} from '../interfaces/view'; +import {BINDING_INDEX, HEADER_OFFSET, RENDERER, TVIEW, T_HOST} from '../interfaces/view'; import {appendChild, createTextNode} from '../node_manipulation'; import {getLView, getSelectedIndex, setIsNotParent} from '../state'; import {NO_CHANGE} from '../tokens'; import {renderStringify} from '../util/misc_utils'; -import {getNativeByIndex} from '../util/view_utils'; import {bind} from './property'; import {getOrCreateTNode, textBindingInternal} from './shared'; @@ -24,19 +22,17 @@ import {getOrCreateTNode, textBindingInternal} from './shared'; * Create static text node * * @param index Index of the node in the data array - * @param value Value to write. This value will be stringified. + * @param value Static string value to write. * * @codeGenApi */ -export function ɵɵtext(index: number, value?: any): void { +export function ɵɵtext(index: number, value: string = ''): void { const lView = getLView(); ngDevMode && assertEqual( lView[BINDING_INDEX], lView[TVIEW].bindingStartIndex, 'text nodes should be created before any bindings'); - ngDevMode && ngDevMode.rendererCreateTextNode++; ngDevMode && assertDataInRange(lView, index + HEADER_OFFSET); const textNative = lView[index + HEADER_OFFSET] = createTextNode(value, lView[RENDERER]); - ngDevMode && ngDevMode.rendererSetText++; const tNode = getOrCreateTNode(lView[TVIEW], lView[T_HOST], index, TNodeType.Element, null, null); // Text nodes are self closing. diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index e6cafe1127c3d..17528fdc35818 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -20,7 +20,6 @@ import {ProceduralRenderer3, RElement, RNode, RText, Renderer3, isProceduralRend import {isLContainer, isLView, isRootView} from './interfaces/type_checks'; import {CHILD_HEAD, CLEANUP, DECLARATION_LCONTAINER, FLAGS, HOST, HookData, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, TVIEW, T_HOST, unusedValueExportToPlacateAjd as unused5} from './interfaces/view'; import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; -import {renderStringify} from './util/misc_utils'; import {findComponentView, getLViewParent} from './util/view_traversal_utils'; import {getNativeByTNode, getNativeByTNodeOrNull, unwrapRNode} from './util/view_utils'; @@ -116,9 +115,11 @@ function applyToElementOrContainer( } } -export function createTextNode(value: any, renderer: Renderer3): RText { - return isProceduralRenderer(renderer) ? renderer.createText(renderStringify(value)) : - renderer.createTextNode(renderStringify(value)); +export function createTextNode(value: string, renderer: Renderer3): RText { + ngDevMode && ngDevMode.rendererCreateTextNode++; + ngDevMode && ngDevMode.rendererSetText++; + return isProceduralRenderer(renderer) ? renderer.createText(value) : + renderer.createTextNode(value); } /** diff --git a/packages/core/test/render3/perf/BUILD.bazel b/packages/core/test/render3/perf/BUILD.bazel index dbf6a9d52b28d..1dd489364bc44 100644 --- a/packages/core/test/render3/perf/BUILD.bazel +++ b/packages/core/test/render3/perf/BUILD.bazel @@ -12,6 +12,14 @@ ts_library( ], ) +ng_rollup_bundle( + name = "element_text_create", + entry_point = ":element_text_create/index.ts", + deps = [ + ":perf_lib", + ], +) + ng_rollup_bundle( name = "interpolation", entry_point = ":interpolation/index.ts", diff --git a/packages/core/test/render3/perf/element_text_create/index.ts b/packages/core/test/render3/perf/element_text_create/index.ts new file mode 100644 index 0000000000000..ce0fc11136baa --- /dev/null +++ b/packages/core/test/render3/perf/element_text_create/index.ts @@ -0,0 +1,76 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import {ɵɵelementEnd, ɵɵelementStart} from '../../../../src/render3/instructions/element'; +import {createTNode, createTView} from '../../../../src/render3/instructions/shared'; +import {ɵɵtext} from '../../../../src/render3/instructions/text'; +import {RenderFlags} from '../../../../src/render3/interfaces/definition'; +import {TNodeType, TViewNode} from '../../../../src/render3/interfaces/node'; +import {createAndRenderLView} from '../setup'; + +`
+ + + + + + + + + + +
+`; +function testTemplate(rf: RenderFlags, ctx: any) { + if (rf & 1) { + ɵɵelementStart(0, 'div'); + ɵɵelementStart(1, 'button'); + ɵɵtext(2, '0'); + ɵɵelementEnd(); + ɵɵelementStart(3, 'button'); + ɵɵtext(4, '1'); + ɵɵelementEnd(); + ɵɵelementStart(5, 'button'); + ɵɵtext(6, '2'); + ɵɵelementEnd(); + ɵɵelementStart(7, 'button'); + ɵɵtext(8, '3'); + ɵɵelementEnd(); + ɵɵelementStart(9, 'button'); + ɵɵtext(10, '4'); + ɵɵelementEnd(); + ɵɵelementStart(11, 'button'); + ɵɵtext(12, '5'); + ɵɵelementEnd(); + ɵɵelementStart(13, 'button'); + ɵɵtext(14, '6'); + ɵɵelementEnd(); + ɵɵelementStart(15, 'button'); + ɵɵtext(16, '7'); + ɵɵelementEnd(); + ɵɵelementStart(17, 'button'); + ɵɵtext(18, '8'); + ɵɵelementEnd(); + ɵɵelementStart(19, 'button'); + ɵɵtext(20, '9'); + ɵɵelementEnd(); + ɵɵelementEnd(); + } +} + +const viewTNode = createTNode(null !, null, TNodeType.View, -1, null, null) as TViewNode; +const embeddedTView = createTView(-1, testTemplate, 21, 0, null, null, null, null); + +// create view once so we don't profile first template pass +createAndRenderLView(null, embeddedTView, viewTNode); + +// profile create views (run templates in creation mode) +console.profile('create'); +for (let i = 0; i < 500000; i++) { + createAndRenderLView(null, embeddedTView, viewTNode); +} +console.profileEnd(); \ No newline at end of file diff --git a/packages/core/test/render3/perf/setup.ts b/packages/core/test/render3/perf/setup.ts index 116922db39978..341502c23f8a8 100644 --- a/packages/core/test/render3/perf/setup.ts +++ b/packages/core/test/render3/perf/setup.ts @@ -9,11 +9,19 @@ import {addToViewTree, createLContainer, createLView, createTNode, createTView, import {ComponentTemplate} from '../../../src/render3/interfaces/definition'; import {TNodeType, TViewNode} from '../../../src/render3/interfaces/node'; import {RComment} from '../../../src/render3/interfaces/renderer'; -import {LView, LViewFlags} from '../../../src/render3/interfaces/view'; +import {LView, LViewFlags, TView} from '../../../src/render3/interfaces/view'; import {insertView} from '../../../src/render3/node_manipulation'; import {NoopRenderer, NoopRendererFactory, WebWorkerRenderNode} from './noop_renderer'; +export function createAndRenderLView( + parentLView: LView | null, tView: TView, hostTNode: TViewNode) { + const embeddedLView = createLView( + parentLView, tView, {}, LViewFlags.CheckAlways, null, hostTNode, new NoopRendererFactory(), + new NoopRenderer()); + renderView(embeddedLView, tView, null); +} + export function setupRootViewWithEmbeddedViews( templateFn: ComponentTemplate| null, consts: number, vars: number, noOfViews: number): LView { diff --git a/tools/public_api_guard/core/core.d.ts b/tools/public_api_guard/core/core.d.ts index 4caac6b04e808..25fbc201053b4 100644 --- a/tools/public_api_guard/core/core.d.ts +++ b/tools/public_api_guard/core/core.d.ts @@ -1087,7 +1087,7 @@ export declare function ɵɵtemplate(index: number, templateFn: ComponentTemplat export declare function ɵɵtemplateRefExtractor(tNode: TNode, currentView: LView): ViewEngine_TemplateRef | null; -export declare function ɵɵtext(index: number, value?: any): void; +export declare function ɵɵtext(index: number, value?: string): void; export declare function ɵɵtextBinding(value: T | NO_CHANGE): void;