From a786a0924929327e2d0cdca007796b179dec4a8e Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Sat, 19 Aug 2023 19:43:38 -0700 Subject: [PATCH] fixup! refactor(core): initial implementation of `{#defer}` block runtime --- .../compiler/src/render3/view/template.ts | 4 + packages/core/src/render3/assert.ts | 3 +- .../core/src/render3/instructions/advance.ts | 4 +- .../core/src/render3/instructions/defer.ts | 185 ++++++++++++------ .../core/src/render3/instructions/shared.ts | 29 ++- .../core/src/render3/instructions/template.ts | 24 +-- .../core/src/render3/interfaces/container.ts | 17 +- packages/core/src/render3/interfaces/defer.ts | 96 +++++++++ .../core/src/render3/interfaces/definition.ts | 2 - packages/core/src/render3/interfaces/node.ts | 83 -------- packages/core/src/render3/interfaces/view.ts | 6 +- 11 files changed, 257 insertions(+), 196 deletions(-) create mode 100644 packages/core/src/render3/interfaces/defer.ts diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index e2c2f90106c478..d1216eed9e298e 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -1132,6 +1132,10 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver this.createDeferTriggerInstructions(deferredIndex, triggers, false); this.createDeferTriggerInstructions(deferredIndex, prefetchTriggers, true); + + // Allocate an extra data slot right after a defer block slot to store + // instance-specific state of that defer block at runtime. + this.allocateDataSlot(); } private createDeferredDepsFunction(name: string, deferred: t.DeferredBlock) { diff --git a/packages/core/src/render3/assert.ts b/packages/core/src/render3/assert.ts index c4212adb957208..b8de75922a4605 100644 --- a/packages/core/src/render3/assert.ts +++ b/packages/core/src/render3/assert.ts @@ -111,8 +111,7 @@ export function assertDirectiveDef(obj: any): asserts obj is DirectiveDef } } -export function assertIndexInDeclRange(lView: LView, index: number) { - const tView = lView[1]; +export function assertIndexInDeclRange(tView: TView, index: number) { assertBetween(HEADER_OFFSET, tView.bindingStartIndex, index); } diff --git a/packages/core/src/render3/instructions/advance.ts b/packages/core/src/render3/instructions/advance.ts index 8ff38fd182850d..23f091f64d751e 100644 --- a/packages/core/src/render3/instructions/advance.ts +++ b/packages/core/src/render3/instructions/advance.ts @@ -8,7 +8,7 @@ import {assertGreaterThan} from '../../util/assert'; import {assertIndexInDeclRange} from '../assert'; import {executeCheckHooks, executeInitAndCheckHooks} from '../hooks'; -import {FLAGS, InitPhaseState, LView, LViewFlags, TView} from '../interfaces/view'; +import {FLAGS, InitPhaseState, LView, LViewFlags, TVIEW, TView} from '../interfaces/view'; import {getLView, getSelectedIndex, getTView, isInCheckNoChangesMode, setSelectedIndex} from '../state'; @@ -43,7 +43,7 @@ export function ɵɵadvance(delta: number): void { export function selectIndexInternal( tView: TView, lView: LView, index: number, checkNoChangesMode: boolean) { - ngDevMode && assertIndexInDeclRange(lView, index); + ngDevMode && assertIndexInDeclRange(lView[TVIEW], index); // Flush the initial hooks for elements in the view that have been added up to this point. // PERF WARNING: do NOT extract this to a separate function without running benchmarks diff --git a/packages/core/src/render3/instructions/defer.ts b/packages/core/src/render3/instructions/defer.ts index a42a8111e9a8bd..c2a2373f71a8d7 100644 --- a/packages/core/src/render3/instructions/defer.ts +++ b/packages/core/src/render3/instructions/defer.ts @@ -7,19 +7,19 @@ */ import {InjectionToken, Injector} from '../../di'; -import {assertDefined, assertEqual, assertNotDefined, throwError} from '../../util/assert'; -import {NgZone} from '../../zone'; -import {assertLContainer} from '../assert'; +import {assertDefined, assertEqual, throwError} from '../../util/assert'; +import {assertIndexInDeclRange, assertLContainer, assertTNodeForLView} from '../assert'; import {bindingUpdated} from '../bindings'; import {getComponentDef, getDirectiveDef, getPipeDef} from '../definition'; -import {DEFER_BLOCK_DETAILS, DeferBlockInstanceState, LContainer} from '../interfaces/container'; -import {DependencyResolverFn, DirectiveDefList, PipeDefList} from '../interfaces/definition'; -import {DeferDependenciesLoadingState, DeferredLoadingBlockConfig, DeferredPlaceholderBlockConfig, TContainerNode, TDeferBlockDetails, TNode} from '../interfaces/node'; +import {DEFER_BLOCK_STATE, DeferBlockInstanceState, LContainer, LDeferBlockDetails} from '../interfaces/container'; +import {DeferDependenciesLoadingState, DeferredLoadingBlockConfig, DeferredPlaceholderBlockConfig, DependencyResolverFn, TDeferBlockDetails} from '../interfaces/defer'; +import {DirectiveDefList, PipeDefList} from '../interfaces/definition'; +import {TContainerNode, TNode} from '../interfaces/node'; import {isDestroyed} from '../interfaces/type_checks'; -import {HEADER_OFFSET, INJECTOR, LView, PARENT, TVIEW} from '../interfaces/view'; +import {HEADER_OFFSET, INJECTOR, LView, PARENT, TVIEW, TView} from '../interfaces/view'; import {getCurrentTNode, getLView, getSelectedTNode, getTView, nextBindingIndex} from '../state'; import {NO_CHANGE} from '../tokens'; -import {getConstant, getTNode, storeLViewOnDestroy} from '../util/view_utils'; +import {getConstant, getTNode, removeLViewOnDestroy, storeLViewOnDestroy} from '../util/view_utils'; import {addLViewToLContainer, createAndRenderEmbeddedLView, removeLViewFromLContainer} from '../view_manipulation'; import {templateInternal} from './template'; @@ -57,33 +57,34 @@ export function ɵɵdefer( const lView = getLView(); const tView = getTView(); const tViewConsts = tView.consts; + const adjustedIndex = index + HEADER_OFFSET; - // Defer block details are needed only once during the first creation pass, - // so we wrap an object with defer block details into a function that is only - // invoked once to avoid re-constructing the same object for each subsequent - // creation run. - const deferBlockConfig: () => TDeferBlockDetails = () => ({ - primaryTmplIndex, - loadingTmplIndex: loadingTmplIndex ?? null, - placeholderTmplIndex: placeholderTmplIndex ?? null, - errorTmplIndex: errorTmplIndex ?? null, - placeholderBlockConfig: placeholderConfigIndex != null ? - getConstant(tViewConsts, placeholderConfigIndex) : - null, - loadingBlockConfig: loadingConfigIndex != null ? - getConstant(tViewConsts, loadingConfigIndex) : - null, - dependencyResolverFn: dependencyResolverFn ?? null, - loadingState: DeferDependenciesLoadingState.NOT_STARTED, - loadingPromise: null, - }); - - templateInternal(index, null, 0, 0, deferBlockConfig); + templateInternal(index, null, 0, 0, null); + + if (tView.firstCreatePass) { + const deferBlockConfig: TDeferBlockDetails = { + primaryTmplIndex, + loadingTmplIndex: loadingTmplIndex ?? null, + placeholderTmplIndex: placeholderTmplIndex ?? null, + errorTmplIndex: errorTmplIndex ?? null, + placeholderBlockConfig: placeholderConfigIndex != null ? + getConstant(tViewConsts, placeholderConfigIndex) : + null, + loadingBlockConfig: loadingConfigIndex != null ? + getConstant(tViewConsts, loadingConfigIndex) : + null, + dependencyResolverFn: dependencyResolverFn ?? null, + loadingState: DeferDependenciesLoadingState.NOT_STARTED, + loadingPromise: null, + }; + + setTDeferBlockDetails(tView, adjustedIndex, deferBlockConfig); + } - // Init instance-specific defer details for this LContainer. - const adjustedIndex = index + HEADER_OFFSET; - const lContainer = lView[adjustedIndex]; - lContainer[DEFER_BLOCK_DETAILS] = {state: DeferBlockInstanceState.INITIAL}; + // Init instance-specific defer details and store it. + const lDetails = []; + lDetails[DEFER_BLOCK_STATE] = DeferBlockInstanceState.INITIAL; + setLDeferBlockDetails(lView, adjustedIndex, lDetails as LDeferBlockDetails); } /** @@ -125,15 +126,24 @@ export function ɵɵdeferOnIdle() { renderPlaceholder(lView, tNode); - const id = _requestIdleCallback(() => { - triggerDeferBlock(lView, tNode); - cancelIdleCallback(id); - }) as number; - storeLViewOnDestroy(lView, () => _cancelIdleCallback(id)); + let id: number; + const removeIdleCallback = () => _cancelIdleCallback(id); + id = _requestIdleCallback(() => { + removeIdleCallback(); + // The idle callback is invoked, we no longer need + // to retain a cleanup callback in an LView. + removeLViewOnDestroy(lView, removeIdleCallback); + triggerDeferBlock(lView, tNode); + }) as number; + + // Store a cleanup function on LView, so that we cancel idle + // callback in case this LView was destroyed before a callback + // was invoked. + storeLViewOnDestroy(lView, removeIdleCallback); } /** - * Creates runtime data structures for the `prefetech on idle` deferred trigger. + * Creates runtime data structures for the `prefetch on idle` deferred trigger. * @codeGenApi */ export function ɵɵdeferPrefetchOnIdle() {} // TODO: implement runtime logic. @@ -146,7 +156,7 @@ export function ɵɵdeferOnImmediate() {} // TODO: implement runtime logic. /** - * Creates runtime data structures for the `prefetech on immediate` deferred trigger. + * Creates runtime data structures for the `prefetch on immediate` deferred trigger. * @codeGenApi */ export function ɵɵdeferPrefetchOnImmediate() {} // TODO: implement runtime logic. @@ -172,7 +182,7 @@ export function ɵɵdeferPrefetchOnTimer(delay: number) {} // TODO: implement r export function ɵɵdeferOnHover() {} // TODO: implement runtime logic. /** - * Creates runtime data structures for the `prefetech on hover` deferred trigger. + * Creates runtime data structures for the `prefetch on hover` deferred trigger. * @codeGenApi */ export function ɵɵdeferPrefetchOnHover() {} // TODO: implement runtime logic. @@ -207,36 +217,79 @@ export function ɵɵdeferPrefetchOnViewport(target?: unknown) {} // TODO: imple /********** Helper functions **********/ +/** + * Calculates a data slot index for defer block info (either static or + * instance-specific), given an index of a defer instruction. + */ +function getDeferBlockDataIndex(deferBlockIndex: number) { + // Instance state is located at the *next* position + // after the defer block slot in an LView or TView.data. + return deferBlockIndex + 1; +} + +/** Retrieves a defer block state from an LView, given a TNode that represents a block. */ +function getLDeferBlockDetails(lView: LView, tNode: TNode): LDeferBlockDetails { + const tView = lView[TVIEW]; + const slotIndex = getDeferBlockDataIndex(tNode.index); + ngDevMode && assertIndexInDeclRange(tView, slotIndex); + return lView[slotIndex]; +} + +/** Stores a defer block instance state in LView. */ +function setLDeferBlockDetails( + lView: LView, deferBlockIndex: number, lDetails: LDeferBlockDetails) { + const tView = lView[TVIEW]; + const slotIndex = getDeferBlockDataIndex(deferBlockIndex); + ngDevMode && assertIndexInDeclRange(tView, slotIndex); + lView[slotIndex] = lDetails; +} + +/** Retrieves static info about a defer block, given a TView and a TNode that represents a block. */ +function getTDeferBlockDetails(tView: TView, tNode: TNode): TDeferBlockDetails { + const slotIndex = getDeferBlockDataIndex(tNode.index); + ngDevMode && assertIndexInDeclRange(tView, slotIndex); + return tView.data[slotIndex] as TDeferBlockDetails; +} + +/** Stores a defer block static info in `TView.data`. */ +function setTDeferBlockDetails( + tView: TView, deferBlockIndex: number, deferBlockConfig: TDeferBlockDetails) { + const slotIndex = getDeferBlockDataIndex(deferBlockIndex); + ngDevMode && assertIndexInDeclRange(tView, slotIndex); + tView.data[slotIndex] = deferBlockConfig; +} + /** * Transitions a defer block to the new state. Updates the necessary * data structures and renders corresponding block. * * @param newState New state that should be applied to the defer block. + * @param tNode TNode that represents a defer block. * @param lContainer Represents an instance of a defer block. * @param stateTmplIndex Index of a template that should be rendered. */ function renderDeferBlockState( - newState: DeferBlockInstanceState, lContainer: LContainer, stateTmplIndex: number|null): void { + newState: DeferBlockInstanceState, tNode: TNode, lContainer: LContainer, + stateTmplIndex: number|null): void { const hostLView = lContainer[PARENT]; // Check if this view is not destroyed. Since the loading process was async, // the view might end up being destroyed by the time rendering happens. if (isDestroyed(hostLView)) return; - ngDevMode && - assertDefined( - lContainer[DEFER_BLOCK_DETAILS], - 'Expected an LContainer that represents ' + - 'a defer block, but got a regular LContainer'); + // Make sure this TNode belongs to TView that represents host LView. + ngDevMode && assertTNodeForLView(tNode, hostLView); + + const lDetails = getLDeferBlockDetails(hostLView, tNode); - const lDetails = lContainer[DEFER_BLOCK_DETAILS]!; + ngDevMode && assertDefined(lDetails, 'Expected a defer block state defined'); // Note: we transition to the next state if the previous state was represented // with a number that is less than the next state. For example, if the current // state is "loading" (represented as `2`), we should not show a placeholder // (represented as `1`). - if (lDetails.state < newState && stateTmplIndex !== null) { - lDetails.state = newState; + if (lDetails[DEFER_BLOCK_STATE] < newState && stateTmplIndex !== null) { + lDetails[DEFER_BLOCK_STATE] = newState; const hostTView = hostLView[TVIEW]; const adjustedIndex = stateTmplIndex + HEADER_OFFSET; const tNode = getTNode(hostTView, adjustedIndex) as TContainerNode; @@ -245,7 +298,7 @@ function renderDeferBlockState( // represents a `{#defer}` block, so always refer to the first one. const viewIndex = 0; removeLViewFromLContainer(lContainer, viewIndex); - const embeddedLView = createAndRenderEmbeddedLView(hostLView, tNode, {}); + const embeddedLView = createAndRenderEmbeddedLView(hostLView, tNode, null); addLViewToLContainer(lContainer, embeddedLView, viewIndex); } } @@ -336,12 +389,13 @@ function triggerResourceLoading( /** Utility function to render `{:placeholder}` content (if present) */ function renderPlaceholder(lView: LView, tNode: TNode) { + const tView = lView[TVIEW]; const lContainer = lView[tNode.index]; ngDevMode && assertLContainer(lContainer); - const tDetails = tNode.value as TDeferBlockDetails; + const tDetails = getTDeferBlockDetails(tView, tNode); renderDeferBlockState( - DeferBlockInstanceState.PLACEHOLDER, lContainer, tDetails.placeholderTmplIndex); + DeferBlockInstanceState.PLACEHOLDER, tNode, lContainer, tDetails.placeholderTmplIndex); } /** @@ -351,9 +405,8 @@ function renderPlaceholder(lView: LView, tNode: TNode) { * @param lContainer Represents an instance of a defer block. * @param tNode Represents defer block info shared across all instances. */ -function renderDeferStateAfterResourceLoading(lContainer: LContainer, tNode: TNode) { - const tDetails = tNode.value as TDeferBlockDetails; - +function renderDeferStateAfterResourceLoading( + tDetails: TDeferBlockDetails, tNode: TNode, lContainer: LContainer) { ngDevMode && assertDefined( tDetails.loadingPromise, 'Expected loading Promise to exist on this defer block'); @@ -364,10 +417,11 @@ function renderDeferStateAfterResourceLoading(lContainer: LContainer, tNode: TNo // Everything is loaded, show the primary block content renderDeferBlockState( - DeferBlockInstanceState.COMPLETE, lContainer, tDetails.primaryTmplIndex); + DeferBlockInstanceState.COMPLETE, tNode, lContainer, tDetails.primaryTmplIndex); } else if (tDetails.loadingState === DeferDependenciesLoadingState.FAILED) { - renderDeferBlockState(DeferBlockInstanceState.ERROR, lContainer, tDetails.errorTmplIndex); + renderDeferBlockState( + DeferBlockInstanceState.ERROR, tNode, lContainer, tDetails.errorTmplIndex); } }); } @@ -378,14 +432,16 @@ function renderDeferStateAfterResourceLoading(lContainer: LContainer, tNode: TNo * no additional actions are taken. */ function triggerDeferBlock(lView: LView, tNode: TNode) { + const tView = lView[TVIEW]; const lContainer = lView[tNode.index]; ngDevMode && assertLContainer(lContainer); - const tDetails = tNode.value as TDeferBlockDetails; + const tDetails = getTDeferBlockDetails(tView, tNode); // Condition is triggered, try to render loading state and start downloading. // Note: if a block is in a loading, completed or an error state, this call would be a noop. - renderDeferBlockState(DeferBlockInstanceState.LOADING, lContainer, tDetails.loadingTmplIndex); + renderDeferBlockState( + DeferBlockInstanceState.LOADING, tNode, lContainer, tDetails.loadingTmplIndex); switch (tDetails.loadingState) { case DeferDependenciesLoadingState.NOT_STARTED: @@ -396,19 +452,20 @@ function triggerDeferBlock(lView: LView, tNode: TNode) { // The `loadingState` might have changed to "loading". if ((tDetails.loadingState as DeferDependenciesLoadingState) === DeferDependenciesLoadingState.IN_PROGRESS) { - renderDeferStateAfterResourceLoading(lContainer, tNode); + renderDeferStateAfterResourceLoading(tDetails, tNode, lContainer); } break; case DeferDependenciesLoadingState.IN_PROGRESS: - renderDeferStateAfterResourceLoading(lContainer, tNode); + renderDeferStateAfterResourceLoading(tDetails, tNode, lContainer); break; case DeferDependenciesLoadingState.COMPLETE: ngDevMode && assertDeferredDependenciesLoaded(tDetails); renderDeferBlockState( - DeferBlockInstanceState.COMPLETE, lContainer, tDetails.primaryTmplIndex); + DeferBlockInstanceState.COMPLETE, tNode, lContainer, tDetails.primaryTmplIndex); break; case DeferDependenciesLoadingState.FAILED: - renderDeferBlockState(DeferBlockInstanceState.ERROR, lContainer, tDetails.errorTmplIndex); + renderDeferBlockState( + DeferBlockInstanceState.ERROR, tNode, lContainer, tDetails.errorTmplIndex); break; default: if (ngDevMode) { diff --git a/packages/core/src/render3/instructions/shared.ts b/packages/core/src/render3/instructions/shared.ts index 429597ea8ace26..0be6f430b4b425 100644 --- a/packages/core/src/render3/instructions/shared.ts +++ b/packages/core/src/render3/instructions/shared.ts @@ -31,7 +31,7 @@ import {CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/container'; import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, HostBindingsFunction, HostDirectiveBindingMap, HostDirectiveDefs, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition'; import {NodeInjectorFactory} from '../interfaces/injector'; import {getUniqueLViewId} from '../interfaces/lview_tracking'; -import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliases, PropertyAliasValue, TAttributes, TConstantsOrFactory, TContainerNode, TDeferBlockDetails, TDirectiveHostNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeType, TProjectionNode} from '../interfaces/node'; +import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliases, PropertyAliasValue, TAttributes, TConstantsOrFactory, TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeType, TProjectionNode} from '../interfaces/node'; import {Renderer} from '../interfaces/renderer'; import {RComment, RElement, RNode, RText} from '../interfaces/renderer_dom'; import {SanitizerFn} from '../interfaces/sanitization'; @@ -140,7 +140,7 @@ export function getOrCreateTNode( tView: TView, index: number, type: TNodeType.Element|TNodeType.Text, name: string|null, attrs: TAttributes|null): TElementNode; export function getOrCreateTNode( - tView: TView, index: number, type: TNodeType.Container, value: string|TDeferBlockDetails|null, + tView: TView, index: number, type: TNodeType.Container, name: string|null, attrs: TAttributes|null): TContainerNode; export function getOrCreateTNode( tView: TView, index: number, type: TNodeType.Projection, name: null, @@ -152,9 +152,8 @@ export function getOrCreateTNode( tView: TView, index: number, type: TNodeType.Icu, name: null, attrs: TAttributes|null): TElementContainerNode; export function getOrCreateTNode( - tView: TView, index: number, type: TNodeType, value: string|TDeferBlockDetails|null, - attrs: TAttributes|null): TElementNode&TContainerNode&TElementContainerNode&TProjectionNode& - TIcuContainerNode { + tView: TView, index: number, type: TNodeType, name: string|null, attrs: TAttributes|null): + TElementNode&TContainerNode&TElementContainerNode&TProjectionNode&TIcuContainerNode { ngDevMode && index !== 0 && // 0 are bogus nodes and they are OK. See `createContainerRef` in // `view_engine_compatibility` for additional context. assertGreaterThanOrEqual(index, HEADER_OFFSET, 'TNodes can\'t be in the LView header.'); @@ -162,7 +161,7 @@ export function getOrCreateTNode( ngDevMode && assertPureTNodeType(type); let tNode = tView.data[index] as TNode; if (tNode === null) { - tNode = createTNodeAtIndex(tView, index, type, value, attrs); + tNode = createTNodeAtIndex(tView, index, type, name, attrs); if (isInI18nBlock()) { // If we are in i18n block then all elements should be pre declared through `Placeholder` // See `TNodeType.Placeholder` and `LFrame.inI18n` for more context. @@ -172,7 +171,7 @@ export function getOrCreateTNode( } } else if (tNode.type & TNodeType.Placeholder) { tNode.type = type; - tNode.value = value; + tNode.value = name; tNode.attrs = attrs; const parent = getCurrentParentTNode(); tNode.injectorIndex = parent === null ? -1 : parent.injectorIndex; @@ -185,14 +184,13 @@ export function getOrCreateTNode( } export function createTNodeAtIndex( - tView: TView, index: number, type: TNodeType, value: string|TDeferBlockDetails|null, - attrs: TAttributes|null) { + tView: TView, index: number, type: TNodeType, name: string|null, attrs: TAttributes|null) { const currentTNode = getCurrentTNodePlaceholderOk(); const isParent = isCurrentTNodeParent(); const parent = isParent ? currentTNode : currentTNode && currentTNode.parent; // Parents cannot cross component boundaries because components will be used in multiple places. const tNode = tView.data[index] = - createTNode(tView, parent as TElementNode | TContainerNode, type, index, value, attrs); + createTNode(tView, parent as TElementNode | TContainerNode, type, index, name, attrs); // Assign a pointer to the first child node of a given view. The first node is not always the one // at index 0, in case of i18n, index 0 can be the instruction `i18nStart` and the first node has // the index 1 or more, so we can't just check node index. @@ -560,7 +558,7 @@ export function storeCleanupWithContext( */ export function createTNode( tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.Container, - index: number, value: string|TDeferBlockDetails|null, attrs: TAttributes|null): TContainerNode; + index: number, tagName: string|null, attrs: TAttributes|null): TContainerNode; export function createTNode( tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType.Element|TNodeType.Text, index: number, tagName: string|null, attrs: TAttributes|null): TElementNode; @@ -575,10 +573,10 @@ export function createTNode( index: number, tagName: string|null, attrs: TAttributes|null): TProjectionNode; export function createTNode( tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType, index: number, - value: string|TDeferBlockDetails|null, attrs: TAttributes|null): TNode; + tagName: string|null, attrs: TAttributes|null): TNode; export function createTNode( tView: TView, tParent: TElementNode|TContainerNode|null, type: TNodeType, index: number, - value: string|TDeferBlockDetails|null, attrs: TAttributes|null): TNode { + value: string|null, attrs: TAttributes|null): TNode { ngDevMode && index !== 0 && // 0 are bogus nodes and they are OK. See `createContainerRef` in // `view_engine_compatibility` for additional context. assertGreaterThanOrEqual(index, HEADER_OFFSET, 'TNodes can\'t be in the LView header.'); @@ -602,8 +600,8 @@ export function createTNode( propertyBindings: null, flags, providerIndexes: 0, - value, - attrs, + value: value, + attrs: attrs, mergedAttrs: null, localNames: null, initialInputs: undefined, @@ -1413,7 +1411,6 @@ export function createLContainer( null, // view refs null, // moved views null, // dehydrated views - null, // defer block details ]; ngDevMode && assertEqual( diff --git a/packages/core/src/render3/instructions/template.ts b/packages/core/src/render3/instructions/template.ts index 50245f0541a63f..d86001e0ada333 100644 --- a/packages/core/src/render3/instructions/template.ts +++ b/packages/core/src/render3/instructions/template.ts @@ -14,7 +14,7 @@ import {assertFirstCreatePass} from '../assert'; import {attachPatchData} from '../context_discovery'; import {registerPostOrderHooks} from '../hooks'; import {ComponentTemplate} from '../interfaces/definition'; -import {LocalRefExtractor, TAttributes, TContainerNode, TDeferBlockDetails, TNode, TNodeType} from '../interfaces/node'; +import {LocalRefExtractor, TAttributes, TContainerNode, TNode, TNodeType} from '../interfaces/node'; import {RComment} from '../interfaces/renderer_dom'; import {isDirectiveHost} from '../interfaces/type_checks'; import {HEADER_OFFSET, HYDRATION, LView, RENDERER, TView, TViewType} from '../interfaces/view'; @@ -26,7 +26,7 @@ import {addToViewTree, createDirectivesInstances, createLContainer, createTView, function templateFirstCreatePass( index: number, tView: TView, lView: LView, templateFn: ComponentTemplate|null, - decls: number, vars: number, value?: string|TDeferBlockDetails|null, attrsIndex?: number|null, + decls: number, vars: number, tagName?: string|null, attrsIndex?: number|null, localRefsIndex?: number|null): TContainerNode { ngDevMode && assertFirstCreatePass(tView); ngDevMode && ngDevMode.firstCreatePass++; @@ -34,7 +34,7 @@ function templateFirstCreatePass( // TODO(pk): refactor getOrCreateTNode to have the "create" only version const tNode = getOrCreateTNode( - tView, index, TNodeType.Container, value || null, + tView, index, TNodeType.Container, tagName || null, getConstant(tViewConsts, attrsIndex)); resolveDirectives(tView, lView, tNode, getConstant(tViewConsts, localRefsIndex)); @@ -81,20 +81,16 @@ export function ɵɵtemplate( export function templateInternal( index: number, templateFn: ComponentTemplate|null, decls: number, vars: number, - value?: string|(() => TDeferBlockDetails)|null, attrsIndex?: number|null, - localRefsIndex?: number|null, localRefExtractor?: LocalRefExtractor) { + tagName?: string|null, attrsIndex?: number|null, localRefsIndex?: number|null, + localRefExtractor?: LocalRefExtractor) { const lView = getLView(); const tView = getTView(); - const adjustedIndex = index + HEADER_OFFSET; - const tNode = tView.firstCreatePass ? - templateFirstCreatePass( - adjustedIndex, tView, lView, templateFn, decls, vars, - // Defer block details are needed only once during the first creation pass, - // so we pass a function and invoke it here to avoid re-constructing the same - // object for each subsequent creation run. - (typeof value === 'function' ? value() : value), attrsIndex, localRefsIndex) : - tView.data[adjustedIndex] as TContainerNode; + + const tNode = tView.firstCreatePass ? templateFirstCreatePass( + adjustedIndex, tView, lView, templateFn, decls, vars, + tagName, attrsIndex, localRefsIndex) : + tView.data[adjustedIndex] as TContainerNode; setCurrentTNode(tNode, false); const comment = _locateOrCreateContainerAnchor(tView, lView, tNode, index) as RComment; diff --git a/packages/core/src/render3/interfaces/container.ts b/packages/core/src/render3/interfaces/container.ts index 6d296a2f1647df..5a823d9b0522b3 100644 --- a/packages/core/src/render3/interfaces/container.ts +++ b/packages/core/src/render3/interfaces/container.ts @@ -47,7 +47,6 @@ export const NATIVE = 7; export const VIEW_REFS = 8; export const MOVED_VIEWS = 9; export const DEHYDRATED_VIEWS = 10; -export const DEFER_BLOCK_DETAILS = 11; /** @@ -56,7 +55,7 @@ export const DEFER_BLOCK_DETAILS = 11; * which views are already in the DOM (and don't need to be re-added) and so we can * remove views from the DOM when they are no longer required. */ -export const CONTAINER_HEADER_OFFSET = 12; +export const CONTAINER_HEADER_OFFSET = 11; /** * Describes the current state of this {#defer} block instance. @@ -78,15 +77,17 @@ export const enum DeferBlockInstanceState { ERROR } +export const DEFER_BLOCK_STATE = 0; + /** * Describes instance-specific {#defer} block data. * - * Note: currently there is only the `state` field, but more fields + * Note: currently there is only the `state` slot, but more slots * would be added later to keep track of `after` and `maximum` features * (which would require per-instance state). */ -export interface LDeferBlockDetails { - state: DeferBlockInstanceState; +export interface LDeferBlockDetails extends Array { + [DEFER_BLOCK_STATE]: DeferBlockInstanceState; } /** @@ -176,12 +177,6 @@ export interface LContainer extends Array { * logic finishes. */ [DEHYDRATED_VIEWS]: DehydratedContainerView[]|null; - - /** - * If this LContainer represents an instance of a `{#defer}` block - - * this field contains instance-specific information about the block. - */ - [DEFER_BLOCK_DETAILS]: LDeferBlockDetails|null; } // Note: This hack is necessary so we don't erroneously get a circular dependency diff --git a/packages/core/src/render3/interfaces/defer.ts b/packages/core/src/render3/interfaces/defer.ts new file mode 100644 index 00000000000000..6595dc8abcfd58 --- /dev/null +++ b/packages/core/src/render3/interfaces/defer.ts @@ -0,0 +1,96 @@ +/** + * @license + * Copyright Google LLC 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 type {DependencyType} from './definition'; + +/** + * Describes the shape of a function generated by the compiler + * to download dependencies that can be defer-loaded. + */ +export type DependencyResolverFn = () => Array>; + +/** + * Describes the state of defer block dependency loading. + */ +export const enum DeferDependenciesLoadingState { + /** Initial state, dependency loading is not yet triggered */ + NOT_STARTED, + + /** Dependency loading is in progress */ + IN_PROGRESS, + + /** Dependency loading has completed successfully */ + COMPLETE, + + /** Dependency loading has failed */ + FAILED, +} + +/** Configuration object for a `{:loading}` block as it is stored in the component constants. */ +export type DeferredLoadingBlockConfig = [minimumTime: number|null, afterTime: number|null]; + +/** Configuration object for a `{:placeholder}` block as it is stored in the component constants. */ +export type DeferredPlaceholderBlockConfig = [afterTime: number|null]; + +/** + * Describes the data shared across all instances of a {#defer} block. + */ +export interface TDeferBlockDetails { + /** + * Index in an LView and TData arrays where a template for the primary content + * can be found. + */ + primaryTmplIndex: number; + + /** + * Index in an LView and TData arrays where a template for the `{:loading}` + * block can be found. + */ + loadingTmplIndex: number|null; + + /** + * Extra configuration parameters (such as `after` and `minimum`) + * for the `{:loading}` block. + */ + loadingBlockConfig: DeferredLoadingBlockConfig|null; + + /** + * Index in an LView and TData arrays where a template for the `{:placeholder}` + * block can be found. + */ + placeholderTmplIndex: number|null; + + /** + * Extra configuration parameters (such as `after` and `minimum`) + * for the `{:placeholder}` block. + */ + placeholderBlockConfig: DeferredPlaceholderBlockConfig|null; + + /** + * Index in an LView and TData arrays where a template for the `{:error}` + * block can be found. + */ + errorTmplIndex: number|null; + + /** + * Compiler-generated function that loads all dependencies for a `{#defer}` block. + */ + dependencyResolverFn: DependencyResolverFn|null; + + /** + * Keeps track of the current loading state of defer block dependencies. + */ + loadingState: DeferDependenciesLoadingState; + + /** + * Dependency loading Promise. This Promise is helpful for cases when there + * are multiple instances of a defer block (e.g. if it was used inside of an *ngFor), + * which all await the same set of dependencies. + */ + loadingPromise: Promise|null; +} diff --git a/packages/core/src/render3/interfaces/definition.ts b/packages/core/src/render3/interfaces/definition.ts index 675f9f291ad792..42a1cb4abe2688 100644 --- a/packages/core/src/render3/interfaces/definition.ts +++ b/packages/core/src/render3/interfaces/definition.ts @@ -495,8 +495,6 @@ export type DependencyType = DirectiveType|ComponentType|PipeType export type DependencyTypeList = Array; -export type DependencyResolverFn = () => Array>; - export type TypeOrFactory = T|(() => T); export type HostBindingsFunction = (rf: RenderFlags, ctx: U) => void; diff --git a/packages/core/src/render3/interfaces/node.ts b/packages/core/src/render3/interfaces/node.ts index bed1a13dbe0010..b51e350cf76158 100644 --- a/packages/core/src/render3/interfaces/node.ts +++ b/packages/core/src/render3/interfaces/node.ts @@ -8,7 +8,6 @@ import {KeyValueArray} from '../../util/array_utils'; import {TStylingRange} from '../interfaces/styling'; -import type {DependencyResolverFn} from './definition'; import {TIcu} from './i18n'; import {CssSelector} from './projection'; import {RNode} from './renderer_dom'; @@ -859,88 +858,6 @@ export interface TProjectionNode extends TNode { value: null; } -/** - * Describes the state of defer block dependency loading. - */ -export const enum DeferDependenciesLoadingState { - /** Initial state, dependency loading is not yet triggered */ - NOT_STARTED, - - /** Dependency loading is in progress */ - IN_PROGRESS, - - /** Dependency loading has completed successfully */ - COMPLETE, - - /** Dependency loading has failed */ - FAILED, -} - -/** Configuration object for a `{:loading}` block as it is stored in the component constants. */ -export type DeferredLoadingBlockConfig = [minimumTime: number|null, afterTime: number|null]; - -/** Configuration object for a `{:placeholder}` block as it is stored in the component constants. */ -export type DeferredPlaceholderBlockConfig = [afterTime: number|null]; - - -/** - * Describes the data shared across all instances of a {#defer} block. - */ -export interface TDeferBlockDetails { - /** - * Index in an LView and TData arrays where a template for the primary content - * can be found. - */ - primaryTmplIndex: number; - - /** - * Index in an LView and TData arrays where a template for the `{:loading}` - * block can be found. - */ - loadingTmplIndex: number|null; - - /** - * Extra configuration parameters (such as `after` and `minimum`) - * for the `{:loading}` block. - */ - loadingBlockConfig: DeferredLoadingBlockConfig|null; - - /** - * Index in an LView and TData arrays where a template for the `{:placeholder}` - * block can be found. - */ - placeholderTmplIndex: number|null; - - /** - * Extra configuration parameters (such as `after` and `minimum`) - * for the `{:placeholder}` block. - */ - placeholderBlockConfig: DeferredPlaceholderBlockConfig|null; - - /** - * Index in an LView and TData arrays where a template for the `{:error}` - * block can be found. - */ - errorTmplIndex: number|null; - - /** - * Compiler-generated function that loads all dependencies for a `{#defer}` block. - */ - dependencyResolverFn: DependencyResolverFn|null; - - /** - * Keeps track of the current loading state of defer block dependencies. - */ - loadingState: DeferDependenciesLoadingState; - - /** - * Dependency loading Promise. This Promise is helpful for cases when there - * are multiple instances of a defer block (e.g. if it was used inside of an *ngFor), - * which all await the same set of dependencies. - */ - loadingPromise: Promise|null; -} - /** * A union type representing all TNode types that can host a directive. */ diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index 02a35f7955047c..93d67c94775b1a 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -23,6 +23,7 @@ import {LQueries, TQueries} from './query'; import {Renderer, RendererFactory} from './renderer'; import {RElement} from './renderer_dom'; import {TStylingKey, TStylingRange} from './styling'; +import {TDeferBlockDetails} from './defer'; @@ -908,8 +909,9 @@ export type DestroyHookData = (HookEntry|HookData)[]; * * Injector bloom filters are also stored here. */ -export type TData = (TNode|PipeDef|DirectiveDef|ComponentDef|number|TStylingRange| - TStylingKey|ProviderToken|TI18n|I18nUpdateOpCodes|TIcu|null|string)[]; +export type TData = + (TNode|PipeDef|DirectiveDef|ComponentDef|number|TStylingRange|TStylingKey| + ProviderToken|TI18n|I18nUpdateOpCodes|TIcu|null|string|TDeferBlockDetails)[]; // Note: This hack is necessary so we don't erroneously get a circular dependency // failure based on types.