@angular/core
Version:
Angular - the core framework
775 lines • 118 kB
JavaScript
/**
* @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 { setActiveConsumer } from '@angular/core/primitives/signals';
import { CachedInjectorService } from '../cached_injector_service';
import { EnvironmentInjector, InjectionToken } from '../di';
import { internalImportProvidersFrom } from '../di/provider_collection';
import { RuntimeError } from '../errors';
import { findMatchingDehydratedView } from '../hydration/views';
import { populateDehydratedViewsInLContainer } from '../linker/view_container_ref';
import { PendingTasks } from '../pending_tasks';
import { assertLContainer, assertTNodeForLView } from '../render3/assert';
import { bindingUpdated } from '../render3/bindings';
import { ChainedInjector } from '../render3/chained_injector';
import { getComponentDef, getDirectiveDef, getPipeDef } from '../render3/definition';
import { getTemplateLocationDetails } from '../render3/instructions/element_validation';
import { markViewDirty } from '../render3/instructions/mark_view_dirty';
import { handleError } from '../render3/instructions/shared';
import { declareTemplate } from '../render3/instructions/template';
import { isDestroyed } from '../render3/interfaces/type_checks';
import { HEADER_OFFSET, INJECTOR, PARENT, TVIEW } from '../render3/interfaces/view';
import { getCurrentTNode, getLView, getSelectedTNode, getTView, nextBindingIndex, } from '../render3/state';
import { isPlatformBrowser } from '../render3/util/misc_utils';
import { getConstant, getTNode, removeLViewOnDestroy, storeLViewOnDestroy, } from '../render3/util/view_utils';
import { addLViewToLContainer, createAndRenderEmbeddedLView, removeLViewFromLContainer, shouldAddViewToDom, } from '../render3/view_manipulation';
import { assertDefined, throwError } from '../util/assert';
import { performanceMarkFeature } from '../util/performance';
import { invokeAllTriggerCleanupFns, invokeTriggerCleanupFns, storeTriggerCleanupFn, } from './cleanup';
import { onHover, onInteraction, onViewport, registerDomTrigger } from './dom_triggers';
import { onIdle } from './idle_scheduler';
import { DEFER_BLOCK_STATE, DeferBlockBehavior, DeferBlockInternalState, DeferBlockState, DeferDependenciesLoadingState, LOADING_AFTER_CLEANUP_FN, NEXT_DEFER_BLOCK_STATE, STATE_IS_FROZEN_UNTIL, } from './interfaces';
import { onTimer, scheduleTimerTrigger } from './timer_scheduler';
import { addDepsToRegistry, assertDeferredDependenciesLoaded, getLDeferBlockDetails, getLoadingBlockAfter, getMinimumDurationForState, getPrimaryBlockTNode, getTDeferBlockDetails, getTemplateIndexForState, setLDeferBlockDetails, setTDeferBlockDetails, } from './utils';
/**
* **INTERNAL**, avoid referencing it in application code.
* *
* Injector token that allows to provide `DeferBlockDependencyInterceptor` class
* implementation.
*
* This token is only injected in devMode
*/
export const DEFER_BLOCK_DEPENDENCY_INTERCEPTOR = new InjectionToken('DEFER_BLOCK_DEPENDENCY_INTERCEPTOR');
/**
* **INTERNAL**, token used for configuring defer block behavior.
*/
export const DEFER_BLOCK_CONFIG = new InjectionToken(ngDevMode ? 'DEFER_BLOCK_CONFIG' : '');
/**
* Returns whether defer blocks should be triggered.
*
* Currently, defer blocks are not triggered on the server,
* only placeholder content is rendered (if provided).
*/
function shouldTriggerDeferBlock(injector) {
const config = injector.get(DEFER_BLOCK_CONFIG, null, { optional: true });
if (config?.behavior === DeferBlockBehavior.Manual) {
return false;
}
return isPlatformBrowser(injector);
}
/**
* Reference to the timer-based scheduler implementation of defer block state
* rendering method. It's used to make timer-based scheduling tree-shakable.
* If `minimum` or `after` parameters are used, compiler generates an extra
* argument for the `ɵɵdefer` instruction, which references a timer-based
* implementation.
*/
let applyDeferBlockStateWithSchedulingImpl = null;
/**
* Enables timer-related scheduling if `after` or `minimum` parameters are setup
* on the `@loading` or `@placeholder` blocks.
*/
export function ɵɵdeferEnableTimerScheduling(tView, tDetails, placeholderConfigIndex, loadingConfigIndex) {
const tViewConsts = tView.consts;
if (placeholderConfigIndex != null) {
tDetails.placeholderBlockConfig = getConstant(tViewConsts, placeholderConfigIndex);
}
if (loadingConfigIndex != null) {
tDetails.loadingBlockConfig = getConstant(tViewConsts, loadingConfigIndex);
}
// Enable implementation that supports timer-based scheduling.
if (applyDeferBlockStateWithSchedulingImpl === null) {
applyDeferBlockStateWithSchedulingImpl = applyDeferBlockStateWithScheduling;
}
}
/**
* Creates runtime data structures for defer blocks.
*
* @param index Index of the `defer` instruction.
* @param primaryTmplIndex Index of the template with the primary block content.
* @param dependencyResolverFn Function that contains dependencies for this defer block.
* @param loadingTmplIndex Index of the template with the loading block content.
* @param placeholderTmplIndex Index of the template with the placeholder block content.
* @param errorTmplIndex Index of the template with the error block content.
* @param loadingConfigIndex Index in the constants array of the configuration of the loading.
* block.
* @param placeholderConfigIndex Index in the constants array of the configuration of the
* placeholder block.
* @param enableTimerScheduling Function that enables timer-related scheduling if `after`
* or `minimum` parameters are setup on the `@loading` or `@placeholder` blocks.
*
* @codeGenApi
*/
export function ɵɵdefer(index, primaryTmplIndex, dependencyResolverFn, loadingTmplIndex, placeholderTmplIndex, errorTmplIndex, loadingConfigIndex, placeholderConfigIndex, enableTimerScheduling) {
const lView = getLView();
const tView = getTView();
const adjustedIndex = index + HEADER_OFFSET;
const tNode = declareTemplate(lView, tView, index, null, 0, 0);
if (tView.firstCreatePass) {
performanceMarkFeature('NgDefer');
const tDetails = {
primaryTmplIndex,
loadingTmplIndex: loadingTmplIndex ?? null,
placeholderTmplIndex: placeholderTmplIndex ?? null,
errorTmplIndex: errorTmplIndex ?? null,
placeholderBlockConfig: null,
loadingBlockConfig: null,
dependencyResolverFn: dependencyResolverFn ?? null,
loadingState: DeferDependenciesLoadingState.NOT_STARTED,
loadingPromise: null,
providers: null,
};
enableTimerScheduling?.(tView, tDetails, placeholderConfigIndex, loadingConfigIndex);
setTDeferBlockDetails(tView, adjustedIndex, tDetails);
}
const lContainer = lView[adjustedIndex];
// If hydration is enabled, looks up dehydrated views in the DOM
// using hydration annotation info and stores those views on LContainer.
// In client-only mode, this function is a noop.
populateDehydratedViewsInLContainer(lContainer, tNode, lView);
// Init instance-specific defer details and store it.
const lDetails = [
null, // NEXT_DEFER_BLOCK_STATE
DeferBlockInternalState.Initial, // DEFER_BLOCK_STATE
null, // STATE_IS_FROZEN_UNTIL
null, // LOADING_AFTER_CLEANUP_FN
null, // TRIGGER_CLEANUP_FNS
null, // PREFETCH_TRIGGER_CLEANUP_FNS
];
setLDeferBlockDetails(lView, adjustedIndex, lDetails);
const cleanupTriggersFn = () => invokeAllTriggerCleanupFns(lDetails);
// When defer block is triggered - unsubscribe from LView destroy cleanup.
storeTriggerCleanupFn(0 /* TriggerType.Regular */, lDetails, () => removeLViewOnDestroy(lView, cleanupTriggersFn));
storeLViewOnDestroy(lView, cleanupTriggersFn);
}
/**
* Loads defer block dependencies when a trigger value becomes truthy.
* @codeGenApi
*/
export function ɵɵdeferWhen(rawValue) {
const lView = getLView();
const bindingIndex = nextBindingIndex();
if (bindingUpdated(lView, bindingIndex, rawValue)) {
const prevConsumer = setActiveConsumer(null);
try {
const value = Boolean(rawValue); // handle truthy or falsy values
const tNode = getSelectedTNode();
const lDetails = getLDeferBlockDetails(lView, tNode);
const renderedState = lDetails[DEFER_BLOCK_STATE];
if (value === false && renderedState === DeferBlockInternalState.Initial) {
// If nothing is rendered yet, render a placeholder (if defined).
renderPlaceholder(lView, tNode);
}
else if (value === true &&
(renderedState === DeferBlockInternalState.Initial ||
renderedState === DeferBlockState.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);
}
}
finally {
setActiveConsumer(prevConsumer);
}
}
}
/**
* Prefetches the deferred content when a value becomes truthy.
* @codeGenApi
*/
export function ɵɵdeferPrefetchWhen(rawValue) {
const lView = getLView();
const bindingIndex = nextBindingIndex();
if (bindingUpdated(lView, bindingIndex, rawValue)) {
const prevConsumer = setActiveConsumer(null);
try {
const value = Boolean(rawValue); // handle truthy or falsy values
const tView = lView[TVIEW];
const tNode = getSelectedTNode();
const tDetails = getTDeferBlockDetails(tView, tNode);
if (value === true && tDetails.loadingState === DeferDependenciesLoadingState.NOT_STARTED) {
// If loading has not been started yet, trigger it now.
triggerPrefetching(tDetails, lView, tNode);
}
}
finally {
setActiveConsumer(prevConsumer);
}
}
}
/**
* Sets up logic to handle the `on idle` deferred trigger.
* @codeGenApi
*/
export function ɵɵdeferOnIdle() {
scheduleDelayedTrigger(onIdle);
}
/**
* Sets up logic to handle the `prefetch on idle` deferred trigger.
* @codeGenApi
*/
export function ɵɵdeferPrefetchOnIdle() {
scheduleDelayedPrefetching(onIdle);
}
/**
* Sets up logic to handle the `on immediate` deferred trigger.
* @codeGenApi
*/
export function ɵɵdeferOnImmediate() {
const lView = getLView();
const tNode = getCurrentTNode();
const tView = lView[TVIEW];
const injector = lView[INJECTOR];
const tDetails = getTDeferBlockDetails(tView, tNode);
// Render placeholder block only if loading template is not present and we're on
// the client to avoid content flickering, since it would be immediately replaced
// by the loading block.
if (!shouldTriggerDeferBlock(injector) || tDetails.loadingTmplIndex === null) {
renderPlaceholder(lView, tNode);
}
triggerDeferBlock(lView, tNode);
}
/**
* Sets up logic to handle the `prefetch on immediate` deferred trigger.
* @codeGenApi
*/
export function ɵɵdeferPrefetchOnImmediate() {
const lView = getLView();
const tNode = getCurrentTNode();
const tView = lView[TVIEW];
const tDetails = getTDeferBlockDetails(tView, tNode);
if (tDetails.loadingState === DeferDependenciesLoadingState.NOT_STARTED) {
triggerResourceLoading(tDetails, lView, tNode);
}
}
/**
* Creates runtime data structures for the `on timer` deferred trigger.
* @param delay Amount of time to wait before loading the content.
* @codeGenApi
*/
export function ɵɵdeferOnTimer(delay) {
scheduleDelayedTrigger(onTimer(delay));
}
/**
* Creates runtime data structures for the `prefetch on timer` deferred trigger.
* @param delay Amount of time to wait before prefetching the content.
* @codeGenApi
*/
export function ɵɵdeferPrefetchOnTimer(delay) {
scheduleDelayedPrefetching(onTimer(delay));
}
/**
* Creates runtime data structures for the `on hover` deferred trigger.
* @param triggerIndex Index at which to find the trigger element.
* @param walkUpTimes Number of times to walk up/down the tree hierarchy to find the trigger.
* @codeGenApi
*/
export function ɵɵdeferOnHover(triggerIndex, walkUpTimes) {
const lView = getLView();
const tNode = getCurrentTNode();
renderPlaceholder(lView, tNode);
registerDomTrigger(lView, tNode, triggerIndex, walkUpTimes, onHover, () => triggerDeferBlock(lView, tNode), 0 /* TriggerType.Regular */);
}
/**
* Creates runtime data structures for the `prefetch on hover` deferred trigger.
* @param triggerIndex Index at which to find the trigger element.
* @param walkUpTimes Number of times to walk up/down the tree hierarchy to find the trigger.
* @codeGenApi
*/
export function ɵɵdeferPrefetchOnHover(triggerIndex, walkUpTimes) {
const lView = getLView();
const tNode = getCurrentTNode();
const tView = lView[TVIEW];
const tDetails = getTDeferBlockDetails(tView, tNode);
if (tDetails.loadingState === DeferDependenciesLoadingState.NOT_STARTED) {
registerDomTrigger(lView, tNode, triggerIndex, walkUpTimes, onHover, () => triggerPrefetching(tDetails, lView, tNode), 1 /* TriggerType.Prefetch */);
}
}
/**
* Creates runtime data structures for the `on interaction` deferred trigger.
* @param triggerIndex Index at which to find the trigger element.
* @param walkUpTimes Number of times to walk up/down the tree hierarchy to find the trigger.
* @codeGenApi
*/
export function ɵɵdeferOnInteraction(triggerIndex, walkUpTimes) {
const lView = getLView();
const tNode = getCurrentTNode();
renderPlaceholder(lView, tNode);
registerDomTrigger(lView, tNode, triggerIndex, walkUpTimes, onInteraction, () => triggerDeferBlock(lView, tNode), 0 /* TriggerType.Regular */);
}
/**
* Creates runtime data structures for the `prefetch on interaction` deferred trigger.
* @param triggerIndex Index at which to find the trigger element.
* @param walkUpTimes Number of times to walk up/down the tree hierarchy to find the trigger.
* @codeGenApi
*/
export function ɵɵdeferPrefetchOnInteraction(triggerIndex, walkUpTimes) {
const lView = getLView();
const tNode = getCurrentTNode();
const tView = lView[TVIEW];
const tDetails = getTDeferBlockDetails(tView, tNode);
if (tDetails.loadingState === DeferDependenciesLoadingState.NOT_STARTED) {
registerDomTrigger(lView, tNode, triggerIndex, walkUpTimes, onInteraction, () => triggerPrefetching(tDetails, lView, tNode), 1 /* TriggerType.Prefetch */);
}
}
/**
* Creates runtime data structures for the `on viewport` deferred trigger.
* @param triggerIndex Index at which to find the trigger element.
* @param walkUpTimes Number of times to walk up/down the tree hierarchy to find the trigger.
* @codeGenApi
*/
export function ɵɵdeferOnViewport(triggerIndex, walkUpTimes) {
const lView = getLView();
const tNode = getCurrentTNode();
renderPlaceholder(lView, tNode);
registerDomTrigger(lView, tNode, triggerIndex, walkUpTimes, onViewport, () => triggerDeferBlock(lView, tNode), 0 /* TriggerType.Regular */);
}
/**
* Creates runtime data structures for the `prefetch on viewport` deferred trigger.
* @param triggerIndex Index at which to find the trigger element.
* @param walkUpTimes Number of times to walk up/down the tree hierarchy to find the trigger.
* @codeGenApi
*/
export function ɵɵdeferPrefetchOnViewport(triggerIndex, walkUpTimes) {
const lView = getLView();
const tNode = getCurrentTNode();
const tView = lView[TVIEW];
const tDetails = getTDeferBlockDetails(tView, tNode);
if (tDetails.loadingState === DeferDependenciesLoadingState.NOT_STARTED) {
registerDomTrigger(lView, tNode, triggerIndex, walkUpTimes, onViewport, () => triggerPrefetching(tDetails, lView, tNode), 1 /* TriggerType.Prefetch */);
}
}
/********** Helper functions **********/
/**
* Schedules triggering of a defer block for `on idle` and `on timer` conditions.
*/
function scheduleDelayedTrigger(scheduleFn) {
const lView = getLView();
const tNode = getCurrentTNode();
renderPlaceholder(lView, tNode);
// Only trigger the scheduled trigger on the browser
// since we don't want to delay the server response.
if (isPlatformBrowser(lView[INJECTOR])) {
const cleanupFn = scheduleFn(() => triggerDeferBlock(lView, tNode), lView);
const lDetails = getLDeferBlockDetails(lView, tNode);
storeTriggerCleanupFn(0 /* TriggerType.Regular */, lDetails, cleanupFn);
}
}
/**
* Schedules prefetching for `on idle` and `on timer` triggers.
*
* @param scheduleFn A function that does the scheduling.
*/
function scheduleDelayedPrefetching(scheduleFn) {
const lView = getLView();
// Only trigger the scheduled trigger on the browser
// since we don't want to delay the server response.
if (isPlatformBrowser(lView[INJECTOR])) {
const tNode = getCurrentTNode();
const tView = lView[TVIEW];
const tDetails = getTDeferBlockDetails(tView, tNode);
if (tDetails.loadingState === DeferDependenciesLoadingState.NOT_STARTED) {
const lDetails = getLDeferBlockDetails(lView, tNode);
const prefetch = () => triggerPrefetching(tDetails, lView, tNode);
const cleanupFn = scheduleFn(prefetch, lView);
storeTriggerCleanupFn(1 /* TriggerType.Prefetch */, lDetails, cleanupFn);
}
}
}
/**
* 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 skipTimerScheduling Indicates that `@loading` and `@placeholder` block
* should be rendered immediately, even if they have `after` or `minimum` config
* options setup. This flag to needed for testing APIs to transition defer block
* between states via `DeferFixture.render` method.
*/
export function renderDeferBlockState(newState, tNode, lContainer, skipTimerScheduling = false) {
const hostLView = lContainer[PARENT];
const hostTView = hostLView[TVIEW];
// 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;
// Make sure this TNode belongs to TView that represents host LView.
ngDevMode && assertTNodeForLView(tNode, hostLView);
const lDetails = getLDeferBlockDetails(hostLView, tNode);
ngDevMode && assertDefined(lDetails, 'Expected a defer block state defined');
const currentState = lDetails[DEFER_BLOCK_STATE];
if (isValidStateChange(currentState, newState) &&
isValidStateChange(lDetails[NEXT_DEFER_BLOCK_STATE] ?? -1, newState)) {
const injector = hostLView[INJECTOR];
const tDetails = getTDeferBlockDetails(hostTView, tNode);
// Skips scheduling on the server since it can delay the server response.
const needsScheduling = !skipTimerScheduling &&
isPlatformBrowser(injector) &&
(getLoadingBlockAfter(tDetails) !== null ||
getMinimumDurationForState(tDetails, DeferBlockState.Loading) !== null ||
getMinimumDurationForState(tDetails, DeferBlockState.Placeholder));
if (ngDevMode && needsScheduling) {
assertDefined(applyDeferBlockStateWithSchedulingImpl, 'Expected scheduling function to be defined');
}
const applyStateFn = needsScheduling
? applyDeferBlockStateWithSchedulingImpl
: applyDeferBlockState;
try {
applyStateFn(newState, lDetails, lContainer, tNode, hostLView);
}
catch (error) {
handleError(hostLView, error);
}
}
}
/**
* Checks whether there is a cached injector associated with a given defer block
* declaration and returns if it exists. If there is no cached injector present -
* creates a new injector and stores in the cache.
*/
function getOrCreateEnvironmentInjector(parentInjector, tDetails, providers) {
return parentInjector
.get(CachedInjectorService)
.getOrCreateInjector(tDetails, parentInjector, providers, ngDevMode ? 'DeferBlock Injector' : '');
}
/**
* Creates a new injector, which contains providers collected from dependencies (NgModules) of
* defer-loaded components. This function detects different types of parent injectors and creates
* a new injector based on that.
*/
function createDeferBlockInjector(parentInjector, tDetails, providers) {
// Check if the parent injector is an instance of a `ChainedInjector`.
//
// In this case, we retain the shape of the injector and use a newly created
// `EnvironmentInjector` as a parent in the `ChainedInjector`. That is needed to
// make sure that the primary injector gets consulted first (since it's typically
// a NodeInjector) and `EnvironmentInjector` tree is consulted after that.
if (parentInjector instanceof ChainedInjector) {
const origInjector = parentInjector.injector;
// Guaranteed to be an environment injector
const parentEnvInjector = parentInjector.parentInjector;
const envInjector = getOrCreateEnvironmentInjector(parentEnvInjector, tDetails, providers);
return new ChainedInjector(origInjector, envInjector);
}
const parentEnvInjector = parentInjector.get(EnvironmentInjector);
// If the `parentInjector` is *not* an `EnvironmentInjector` - we need to create
// a new `ChainedInjector` with the following setup:
//
// - the provided `parentInjector` becomes a primary injector
// - an existing (real) `EnvironmentInjector` becomes a parent injector for
// a newly-created one, which contains extra providers
//
// So the final order in which injectors would be consulted in this case would look like this:
//
// 1. Provided `parentInjector`
// 2. Newly-created `EnvironmentInjector` with extra providers
// 3. `EnvironmentInjector` from the `parentInjector`
if (parentEnvInjector !== parentInjector) {
const envInjector = getOrCreateEnvironmentInjector(parentEnvInjector, tDetails, providers);
return new ChainedInjector(parentInjector, envInjector);
}
// The `parentInjector` is an instance of an `EnvironmentInjector`.
// No need for special handling, we can use `parentInjector` as a
// parent injector directly.
return getOrCreateEnvironmentInjector(parentInjector, tDetails, providers);
}
/**
* Applies changes to the DOM to reflect a given state.
*/
function applyDeferBlockState(newState, lDetails, lContainer, tNode, hostLView) {
const stateTmplIndex = getTemplateIndexForState(newState, hostLView, tNode);
if (stateTmplIndex !== null) {
lDetails[DEFER_BLOCK_STATE] = newState;
const hostTView = hostLView[TVIEW];
const adjustedIndex = stateTmplIndex + HEADER_OFFSET;
const activeBlockTNode = getTNode(hostTView, adjustedIndex);
// There is only 1 view that can be present in an LContainer that
// represents a defer block, so always refer to the first one.
const viewIndex = 0;
removeLViewFromLContainer(lContainer, viewIndex);
let injector;
if (newState === DeferBlockState.Complete) {
// When we render a defer block in completed state, there might be
// newly loaded standalone components used within the block, which may
// import NgModules with providers. In order to make those providers
// available for components declared in that NgModule, we create an instance
// of an environment injector to host those providers and pass this injector
// to the logic that creates a view.
const tDetails = getTDeferBlockDetails(hostTView, tNode);
const providers = tDetails.providers;
if (providers && providers.length > 0) {
injector = createDeferBlockInjector(hostLView[INJECTOR], tDetails, providers);
}
}
const dehydratedView = findMatchingDehydratedView(lContainer, activeBlockTNode.tView.ssrId);
const embeddedLView = createAndRenderEmbeddedLView(hostLView, activeBlockTNode, null, {
dehydratedView,
injector,
});
addLViewToLContainer(lContainer, embeddedLView, viewIndex, shouldAddViewToDom(activeBlockTNode, dehydratedView));
markViewDirty(embeddedLView, 2 /* NotificationSource.DeferBlockStateUpdate */);
}
}
/**
* Extends the `applyDeferBlockState` with timer-based scheduling.
* This function becomes available on a page if there are defer blocks
* that use `after` or `minimum` parameters in the `@loading` or
* `@placeholder` blocks.
*/
function applyDeferBlockStateWithScheduling(newState, lDetails, lContainer, tNode, hostLView) {
const now = Date.now();
const hostTView = hostLView[TVIEW];
const tDetails = getTDeferBlockDetails(hostTView, tNode);
if (lDetails[STATE_IS_FROZEN_UNTIL] === null || lDetails[STATE_IS_FROZEN_UNTIL] <= now) {
lDetails[STATE_IS_FROZEN_UNTIL] = null;
const loadingAfter = getLoadingBlockAfter(tDetails);
const inLoadingAfterPhase = lDetails[LOADING_AFTER_CLEANUP_FN] !== null;
if (newState === DeferBlockState.Loading && loadingAfter !== null && !inLoadingAfterPhase) {
// Trying to render loading, but it has an `after` config,
// so schedule an update action after a timeout.
lDetails[NEXT_DEFER_BLOCK_STATE] = newState;
const cleanupFn = scheduleDeferBlockUpdate(loadingAfter, lDetails, tNode, lContainer, hostLView);
lDetails[LOADING_AFTER_CLEANUP_FN] = cleanupFn;
}
else {
// If we transition to a complete or an error state and there is a pending
// operation to render loading after a timeout - invoke a cleanup operation,
// which stops the timer.
if (newState > DeferBlockState.Loading && inLoadingAfterPhase) {
lDetails[LOADING_AFTER_CLEANUP_FN]();
lDetails[LOADING_AFTER_CLEANUP_FN] = null;
lDetails[NEXT_DEFER_BLOCK_STATE] = null;
}
applyDeferBlockState(newState, lDetails, lContainer, tNode, hostLView);
const duration = getMinimumDurationForState(tDetails, newState);
if (duration !== null) {
lDetails[STATE_IS_FROZEN_UNTIL] = now + duration;
scheduleDeferBlockUpdate(duration, lDetails, tNode, lContainer, hostLView);
}
}
}
else {
// We are still rendering the previous state.
// Update the `NEXT_DEFER_BLOCK_STATE`, which would be
// picked up once it's time to transition to the next state.
lDetails[NEXT_DEFER_BLOCK_STATE] = newState;
}
}
/**
* Schedules an update operation after a specified timeout.
*/
function scheduleDeferBlockUpdate(timeout, lDetails, tNode, lContainer, hostLView) {
const callback = () => {
const nextState = lDetails[NEXT_DEFER_BLOCK_STATE];
lDetails[STATE_IS_FROZEN_UNTIL] = null;
lDetails[NEXT_DEFER_BLOCK_STATE] = null;
if (nextState !== null) {
renderDeferBlockState(nextState, tNode, lContainer);
}
};
return scheduleTimerTrigger(timeout, callback, hostLView);
}
/**
* Checks whether we can transition to the next state.
*
* 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 `1`), we should not show a placeholder
* (represented as `0`), but we can show a completed state (represented as `2`)
* or an error state (represented as `3`).
*/
function isValidStateChange(currentState, newState) {
return currentState < newState;
}
/**
* Trigger prefetching of dependencies for a defer block.
*
* @param tDetails Static information about this defer block.
* @param lView LView of a host view.
*/
export function triggerPrefetching(tDetails, lView, tNode) {
if (lView[INJECTOR] && shouldTriggerDeferBlock(lView[INJECTOR])) {
triggerResourceLoading(tDetails, lView, tNode);
}
}
/**
* Trigger loading of defer block dependencies if the process hasn't started yet.
*
* @param tDetails Static information about this defer block.
* @param lView LView of a host view.
*/
export function triggerResourceLoading(tDetails, lView, tNode) {
const injector = lView[INJECTOR];
const tView = lView[TVIEW];
if (tDetails.loadingState !== DeferDependenciesLoadingState.NOT_STARTED) {
// If the loading status is different from initial one, it means that
// the loading of dependencies is in progress and there is nothing to do
// in this function. All details can be obtained from the `tDetails` object.
return tDetails.loadingPromise ?? Promise.resolve();
}
const lDetails = getLDeferBlockDetails(lView, tNode);
const primaryBlockTNode = getPrimaryBlockTNode(tView, tDetails);
// Switch from NOT_STARTED -> IN_PROGRESS state.
tDetails.loadingState = DeferDependenciesLoadingState.IN_PROGRESS;
// Prefetching is triggered, cleanup all registered prefetch triggers.
invokeTriggerCleanupFns(1 /* TriggerType.Prefetch */, lDetails);
let dependenciesFn = tDetails.dependencyResolverFn;
if (ngDevMode) {
// Check if dependency function interceptor is configured.
const deferDependencyInterceptor = injector.get(DEFER_BLOCK_DEPENDENCY_INTERCEPTOR, null, {
optional: true,
});
if (deferDependencyInterceptor) {
dependenciesFn = deferDependencyInterceptor.intercept(dependenciesFn);
}
}
// Indicate that an application is not stable and has a pending task.
const pendingTasks = injector.get(PendingTasks);
const taskId = pendingTasks.add();
// The `dependenciesFn` might be `null` when all dependencies within
// a given defer block were eagerly referenced elsewhere in a file,
// thus no dynamic `import()`s were produced.
if (!dependenciesFn) {
tDetails.loadingPromise = Promise.resolve().then(() => {
tDetails.loadingPromise = null;
tDetails.loadingState = DeferDependenciesLoadingState.COMPLETE;
pendingTasks.remove(taskId);
});
return tDetails.loadingPromise;
}
// Start downloading of defer block dependencies.
tDetails.loadingPromise = Promise.allSettled(dependenciesFn()).then((results) => {
let failed = false;
const directiveDefs = [];
const pipeDefs = [];
for (const result of results) {
if (result.status === 'fulfilled') {
const dependency = result.value;
const directiveDef = getComponentDef(dependency) || getDirectiveDef(dependency);
if (directiveDef) {
directiveDefs.push(directiveDef);
}
else {
const pipeDef = getPipeDef(dependency);
if (pipeDef) {
pipeDefs.push(pipeDef);
}
}
}
else {
failed = true;
break;
}
}
// Loading is completed, we no longer need the loading Promise
// and a pending task should also be removed.
tDetails.loadingPromise = null;
pendingTasks.remove(taskId);
if (failed) {
tDetails.loadingState = DeferDependenciesLoadingState.FAILED;
if (tDetails.errorTmplIndex === null) {
const templateLocation = ngDevMode ? getTemplateLocationDetails(lView) : '';
const error = new RuntimeError(750 /* RuntimeErrorCode.DEFER_LOADING_FAILED */, ngDevMode &&
'Loading dependencies for `@defer` block failed, ' +
`but no \`@error\` block was configured${templateLocation}. ` +
'Consider using the `@error` block to render an error state.');
handleError(lView, error);
}
}
else {
tDetails.loadingState = DeferDependenciesLoadingState.COMPLETE;
// Update directive and pipe registries to add newly downloaded dependencies.
const primaryBlockTView = primaryBlockTNode.tView;
if (directiveDefs.length > 0) {
primaryBlockTView.directiveRegistry = addDepsToRegistry(primaryBlockTView.directiveRegistry, directiveDefs);
// Extract providers from all NgModules imported by standalone components
// used within this defer block.
const directiveTypes = directiveDefs.map((def) => def.type);
const providers = internalImportProvidersFrom(false, ...directiveTypes);
tDetails.providers = providers;
}
if (pipeDefs.length > 0) {
primaryBlockTView.pipeRegistry = addDepsToRegistry(primaryBlockTView.pipeRegistry, pipeDefs);
}
}
});
return tDetails.loadingPromise;
}
/** Utility function to render placeholder content (if present) */
function renderPlaceholder(lView, tNode) {
const lContainer = lView[tNode.index];
ngDevMode && assertLContainer(lContainer);
renderDeferBlockState(DeferBlockState.Placeholder, tNode, lContainer);
}
/**
* Subscribes to the "loading" Promise and renders corresponding defer sub-block,
* based on the loading results.
*
* @param lContainer Represents an instance of a defer block.
* @param tNode Represents defer block info shared across all instances.
*/
function renderDeferStateAfterResourceLoading(tDetails, tNode, lContainer) {
ngDevMode &&
assertDefined(tDetails.loadingPromise, 'Expected loading Promise to exist on this defer block');
tDetails.loadingPromise.then(() => {
if (tDetails.loadingState === DeferDependenciesLoadingState.COMPLETE) {
ngDevMode && assertDeferredDependenciesLoaded(tDetails);
// Everything is loaded, show the primary block content
renderDeferBlockState(DeferBlockState.Complete, tNode, lContainer);
}
else if (tDetails.loadingState === DeferDependenciesLoadingState.FAILED) {
renderDeferBlockState(DeferBlockState.Error, tNode, lContainer);
}
});
}
/**
* Attempts to trigger loading of defer block dependencies.
* If the block is already in a loading, completed or an error state -
* no additional actions are taken.
*/
function triggerDeferBlock(lView, tNode) {
const tView = lView[TVIEW];
const lContainer = lView[tNode.index];
const injector = lView[INJECTOR];
ngDevMode && assertLContainer(lContainer);
if (!shouldTriggerDeferBlock(injector))
return;
const lDetails = getLDeferBlockDetails(lView, tNode);
const tDetails = getTDeferBlockDetails(tView, tNode);
// Defer block is triggered, cleanup all registered trigger functions.
invokeAllTriggerCleanupFns(lDetails);
switch (tDetails.loadingState) {
case DeferDependenciesLoadingState.NOT_STARTED:
renderDeferBlockState(DeferBlockState.Loading, tNode, lContainer);
triggerResourceLoading(tDetails, lView, tNode);
// The `loadingState` might have changed to "loading".
if (tDetails.loadingState ===
DeferDependenciesLoadingState.IN_PROGRESS) {
renderDeferStateAfterResourceLoading(tDetails, tNode, lContainer);
}
break;
case DeferDependenciesLoadingState.IN_PROGRESS:
renderDeferBlockState(DeferBlockState.Loading, tNode, lContainer);
renderDeferStateAfterResourceLoading(tDetails, tNode, lContainer);
break;
case DeferDependenciesLoadingState.COMPLETE:
ngDevMode && assertDeferredDependenciesLoaded(tDetails);
renderDeferBlockState(DeferBlockState.Complete, tNode, lContainer);
break;
case DeferDependenciesLoadingState.FAILED:
renderDeferBlockState(DeferBlockState.Error, tNode, lContainer);
break;
default:
if (ngDevMode) {
throwError('Unknown defer block state');
}
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5zdHJ1Y3Rpb25zLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vLi4vcGFja2FnZXMvY29yZS9zcmMvZGVmZXIvaW5zdHJ1Y3Rpb25zLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7R0FNRztBQUVILE9BQU8sRUFBQyxpQkFBaUIsRUFBQyxNQUFNLGtDQUFrQyxDQUFDO0FBRW5FLE9BQU8sRUFBQyxxQkFBcUIsRUFBQyxNQUFNLDRCQUE0QixDQUFDO0FBRWpFLE9BQU8sRUFBQyxtQkFBbUIsRUFBRSxjQUFjLEVBQXFCLE1BQU0sT0FBTyxDQUFDO0FBQzlFLE9BQU8sRUFBQywyQkFBMkIsRUFBQyxNQUFNLDJCQUEyQixDQUFDO0FBQ3RFLE9BQU8sRUFBQyxZQUFZLEVBQW1CLE1BQU0sV0FBVyxDQUFDO0FBQ3pELE9BQU8sRUFBQywwQkFBMEIsRUFBQyxNQUFNLG9CQUFvQixDQUFDO0FBQzlELE9BQU8sRUFBQyxtQ0FBbUMsRUFBQyxNQUFNLDhCQUE4QixDQUFDO0FBQ2pGLE9BQU8sRUFBQyxZQUFZLEVBQUMsTUFBTSxrQkFBa0IsQ0FBQztBQUM5QyxPQUFPLEVBQUMsZ0JBQWdCLEVBQUUsbUJBQW1CLEVBQUMsTUFBTSxtQkFBbUIsQ0FBQztBQUN4RSxPQUFPLEVBQUMsY0FBYyxFQUFDLE1BQU0scUJBQXFCLENBQUM7QUFDbkQsT0FBTyxFQUFDLGVBQWUsRUFBQyxNQUFNLDZCQUE2QixDQUFDO0FBQzVELE9BQU8sRUFBQyxlQUFlLEVBQUUsZUFBZSxFQUFFLFVBQVUsRUFBQyxNQUFNLHVCQUF1QixDQUFDO0FBQ25GLE9BQU8sRUFBQywwQkFBMEIsRUFBQyxNQUFNLDRDQUE0QyxDQUFDO0FBQ3RGLE9BQU8sRUFBQyxhQUFhLEVBQUMsTUFBTSx5Q0FBeUMsQ0FBQztBQUN0RSxPQUFPLEVBQUMsV0FBVyxFQUFDLE1BQU0sZ0NBQWdDLENBQUM7QUFDM0QsT0FBTyxFQUFDLGVBQWUsRUFBQyxNQUFNLGtD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