diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/GOLDEN_PARTIAL.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/GOLDEN_PARTIAL.js
index e4bcf5223e2366..69fbedda22f5a7 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/GOLDEN_PARTIAL.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/GOLDEN_PARTIAL.js
@@ -13,6 +13,7 @@ MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-
{{message}}
{#defer}Deferred content{/defer}
+
Content after defer block
`, isInline: true });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{
@@ -22,6 +23,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDE
{{message}}
{#defer}Deferred content{/defer}
+
Content after defer block
`,
}]
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/basic_deferred.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/basic_deferred.ts
index 316a889e9cf102..9063b510adb821 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/basic_deferred.ts
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/basic_deferred.ts
@@ -5,6 +5,7 @@ import {Component} from '@angular/core';
{{message}}
{#defer}Deferred content{/defer}
+
Content after defer block
`,
})
diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/basic_deferred_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/basic_deferred_template.js
index d28a3a68387d3e..00d4b1884a7059 100644
--- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/basic_deferred_template.js
+++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/basic_deferred_template.js
@@ -13,7 +13,9 @@ MyApp.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
$r3$.ɵɵtemplate(2, MyApp_Defer_2_Template, 1, 0);
$r3$.ɵɵdefer(3, 2);
$r3$.ɵɵdeferOnIdle();
- $r3$.ɵɵelementEnd();
+ $r3$.ɵɵelementStart(5, "p");
+ $r3$.ɵɵtext(6, "Content after defer block");
+ $r3$.ɵɵelementEnd()();
}
if (rf & 2) {
$r3$.ɵɵadvance(1);
diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts
index 8c166438173fc9..36a937e929eb82 100644
--- a/packages/compiler/src/render3/view/template.ts
+++ b/packages/compiler/src/render3/view/template.ts
@@ -1300,6 +1300,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..e0af79ddf32ffb 100644
--- a/packages/core/src/render3/instructions/defer.ts
+++ b/packages/core/src/render3/instructions/defer.ts
@@ -7,22 +7,22 @@
*/
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 {LContainer} from '../interfaces/container';
+import {DEFER_BLOCK_STATE, DeferBlockInstanceState, DeferDependenciesLoadingState, DeferredLoadingBlockConfig, DeferredPlaceholderBlockConfig, DependencyResolverFn, LDeferBlockDetails, 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';
+import {ɵɵtemplate} from './template';
/**
* Shims for the `requestIdleCallback` and `cancelIdleCallback` functions for environments
@@ -57,53 +57,59 @@ 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);
+ ɵɵtemplate(index, null, 0, 0);
+
+ 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);
}
/**
- * Loads the deferred content when a value becomes truthy.
+ * Loads defer block dependencies when a trigger value becomes truthy.
* @codeGenApi
*/
export function ɵɵdeferWhen(value: unknown) {
const lView = getLView();
const bindingIndex = nextBindingIndex();
const newValue = !!value; // handle truthy or falsy values
- const oldValue = lView[bindingIndex];
- // If an old value was `true` - don't enter the path that triggers
- // defer loading.
- if (oldValue !== true && bindingUpdated(lView, bindingIndex, value)) {
+
+ if (bindingUpdated(lView, bindingIndex, value)) {
const tNode = getSelectedTNode();
- if (oldValue === NO_CHANGE && newValue === false) {
- // We set the value for the first time, render a placeholder (if defined).
+ const lDetails = getLDeferBlockDetails(lView, tNode);
+ const renderedState = lDetails[DEFER_BLOCK_STATE];
+ if (newValue === false && renderedState === DeferBlockInstanceState.INITIAL) {
+ // If nothing is rendered yet, render a placeholder (if defined).
renderPlaceholder(lView, tNode);
- } else if (newValue === true) {
- // The `when` condition has changed to `true`, trigger defer block loading.
+ } else if (
+ newValue === true &&
+ (renderedState === DeferBlockInstanceState.INITIAL ||
+ renderedState === DeferBlockInstanceState.PLACEHOLDER)) {
+ // The `when` condition has changed to `true`, trigger defer block loading
+ // if the block is either in initial (nothing is rendered) or a placeholder
+ // state.
triggerDeferBlock(lView, tNode);
}
}
@@ -125,15 +131,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 +161,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 +187,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 +222,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 +303,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 +394,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 +410,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 +422,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 +437,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 +457,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..f747f5974ec14b 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));
@@ -75,26 +75,14 @@ export function ɵɵtemplate(
index: number, templateFn: ComponentTemplate|null, decls: number, vars: number,
tagName?: string|null, attrsIndex?: number|null, localRefsIndex?: number|null,
localRefExtractor?: LocalRefExtractor) {
- templateInternal(
- index, templateFn, decls, vars, tagName, attrsIndex, localRefsIndex, localRefExtractor);
-}
-
-export function templateInternal(
- index: number, templateFn: ComponentTemplate|null, decls: number, vars: number,
- value?: string|(() => TDeferBlockDetails)|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..524874ea4f5735 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,38 +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;
-
-/**
- * Describes the current state of this {#defer} block instance.
- */
-export const enum DeferBlockInstanceState {
- /** Initial state, nothing is rendered yet */
- INITIAL,
-
- /** The {:placeholder} block content is rendered */
- PLACEHOLDER,
-
- /** The {:loading} block content is rendered */
- LOADING,
-
- /** The main content block content is rendered */
- COMPLETE,
-
- /** The {:error} block content is rendered */
- ERROR
-}
-
-/**
- * Describes instance-specific {#defer} block data.
- *
- * Note: currently there is only the `state` field, but more fields
- * would be added later to keep track of `after` and `maximum` features
- * (which would require per-instance state).
- */
-export interface LDeferBlockDetails {
- state: DeferBlockInstanceState;
-}
+export const CONTAINER_HEADER_OFFSET = 11;
/**
* The state associated with a container.
@@ -176,12 +144,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..9fdb0891043c24
--- /dev/null
+++ b/packages/core/src/render3/interfaces/defer.ts
@@ -0,0 +1,129 @@
+/**
+ * @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;
+}
+
+/**
+ * Describes the current state of this {#defer} block instance.
+ */
+export const enum DeferBlockInstanceState {
+ /** Initial state, nothing is rendered yet */
+ INITIAL,
+
+ /** The {:placeholder} block content is rendered */
+ PLACEHOLDER,
+
+ /** The {:loading} block content is rendered */
+ LOADING,
+
+ /** The main content block content is rendered */
+ COMPLETE,
+
+ /** The {:error} block content is rendered */
+ ERROR
+}
+
+export const DEFER_BLOCK_STATE = 0;
+
+/**
+ * Describes instance-specific {#defer} block data.
+ *
+ * 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 extends Array {
+ [DEFER_BLOCK_STATE]: DeferBlockInstanceState;
+}
diff --git a/packages/core/src/render3/interfaces/definition.ts b/packages/core/src/render3/interfaces/definition.ts
index 7dd4a508380632..43bf1db22f30bf 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.
diff --git a/packages/core/test/acceptance/defer_spec.ts b/packages/core/test/acceptance/defer_spec.ts
index aa3660d79924b7..a60479352e876c 100644
--- a/packages/core/test/acceptance/defer_spec.ts
+++ b/packages/core/test/acceptance/defer_spec.ts
@@ -14,7 +14,7 @@ describe('#defer', () => {
beforeEach(() => setEnabledBlockTypes(['defer']));
afterEach(() => setEnabledBlockTypes([]));
- it('should work in simple cases', async () => {
+ it('should transition between placeholder, loading and loaded states', async () => {
@Component({
selector: 'my-lazy-cmp',
standalone: true,